Skip to content

Commit

Permalink
feat: add map props feature
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronauck committed Apr 28, 2018
1 parent a7f5eb4 commit 1ae14f8
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 9 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Usage](#--usage-demo)
- [Working with new Context api](#working-with-new-context-api)
- [Custom render and retrieving props from composed](#custom-render-and-retrieving-props-from-composed)
- [Mapping props from mappers](#mapping-props-from-mappers)
- [Leading with multiple params](#leading-with-multiple-params)
- [Typescript support](#typescript-support)
- [Inline composition](#inline-composition)
Expand Down Expand Up @@ -133,6 +134,32 @@ const Composed = adopt({
</Composed>
```

### Mapping props from mappers

Sometimes get properties from your mappers can be kind a boring depending how nest is the result from each mapper. To easily avoid deep nested objects or combine your results, you can map the final results into a single object using de `mapProps` function as second parameter.

```js
import { adopt } from 'react-adopt'
import { Value } from 'react-powerplug'

const mapper = {
greet: <Value initial="Hi" />,
name: <Value initial="John" />,
}

const mapProps = ({ greet, name }) => ({
message: `${greet.value} ${name.value}`,
})

const Composed = adopt(mapper, mapProps)

const App = () => (
<Composed>
{({ message }) => /* ... */}
</Composed>
)
```

### Leading with multiple params

Some render props components return multiple arguments in the children function instead of a single one (see a simple example in the new [Query](https://www.apollographql.com/docs/react/essentials/queries.html#basic) and [Mutation](https://www.apollographql.com/docs/react/essentials/mutations.html) component from `react-apollo`). In this case, you can do an arbitrary render with `render` prop [using your map value as a function](#custom-render-and-retrieving-props-from-composed):
Expand Down
39 changes: 39 additions & 0 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,42 @@ test('inline composition using <Adopt> component', () => {
expect(result.children().length).toBe(1)
expect(result.html()).toBe('<div>foo</div>')
})

test('mapping props as second parameter of adopt()', () => {
const children = jest.fn(() => null)

const Composed = adopt(
{
foo: <Value initial="foo" />,
bar: <Value initial="bar" />,
},
({ foo, bar }) => ({
foobar: foo.value + bar.value,
})
)

mount(<Composed>{children}</Composed>)

expect(children).toHaveBeenCalledWith({ foobar: 'foobar' })
})

test('mapping props as prop of <Adopt />', () => {
const children = jest.fn(() => null)

const mapper = {
foo: <Value initial="foo" />,
bar: <Value initial="bar" />,
}

const mapProps = ({ foo, bar }) => ({
foobar: foo.value + bar.value,
})

mount(
<Adopt mapper={mapper} mapProps={mapProps}>
{children}
</Adopt>
)

expect(children).toHaveBeenCalledWith({ foobar: 'foobar' })
})
31 changes: 22 additions & 9 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,45 @@ export declare type MapperValue<RP, P> =

export declare type Mapper<RP, P> = Record<keyof RP, MapperValue<RP, P>>

export function adopt<RP = any, P = any>(mapper: Mapper<RP, P>): RPC<RP, P> {
export declare type MapProps<RP> = (props: any) => RP

export function adopt<RP = any, P = any>(
mapper: Mapper<RP, P>,
mapProps?: MapProps<RP>
): RPC<RP, P> {
if (!values(mapper).some(isValidRenderProp)) {
throw new Error(
'The render props object mapper just accept valid elements as value'
)
}

const mapperKeys = keys(mapper)
const Children: any = ({ children, ...rest }: any) =>
isFn(children) && children(rest)

const reducer = (Component: RPC<RP>, key: string): RPC<RP> => ({
const reducer = (Component: RPC<RP>, key: string, idx: number): RPC<RP> => ({
children,
...rest
}) => (
<Component {...rest}>
{props => {
const element = prop(key, mapper)
const propsWithoutRest = omit<RP>(keys(rest), props)
const isLast = idx === mapperKeys.length - 1

const render: ChildrenFn<RP> = cProps => {
const renderProps = assign({}, propsWithoutRest, {
[key]: cProps,
})

const render: ChildrenFn<RP> = cProps =>
isFn(children)
return isFn(children)
? children(
assign({}, propsWithoutRest, {
[key]: cProps,
})
mapProps && isFn(mapProps) && isLast
? mapProps(renderProps)
: renderProps
)
: null
}

return isFn(element)
? React.createElement(element, assign({}, rest, props, { render }))
Expand All @@ -79,20 +91,21 @@ export function adopt<RP = any, P = any>(mapper: Mapper<RP, P>): RPC<RP, P> {
</Component>
)

return keys(mapper).reduce(reducer, Children)
return mapperKeys.reduce(reducer, Children)
}

export type AdoptProps<RP, P> = P & {
mapper: Mapper<RP, P>
children: ChildrenFn<RP>
mapProps?: MapProps<RP>
}

export class Adopt extends React.Component<AdoptProps<any, any>> {
Composed: React.ComponentType<any>

constructor(props: any) {
super(props)
this.Composed = adopt(props.mapper)
this.Composed = adopt(props.mapper, this.props.mapProps)
}

public render(): JSX.Element {
Expand Down

0 comments on commit 1ae14f8

Please sign in to comment.