Skip to content

Commit 1ae14f8

Browse files
committed
feat: add map props feature
1 parent a7f5eb4 commit 1ae14f8

File tree

3 files changed

+88
-9
lines changed

3 files changed

+88
-9
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Usage](#--usage-demo)
1515
- [Working with new Context api](#working-with-new-context-api)
1616
- [Custom render and retrieving props from composed](#custom-render-and-retrieving-props-from-composed)
17+
- [Mapping props from mappers](#mapping-props-from-mappers)
1718
- [Leading with multiple params](#leading-with-multiple-params)
1819
- [Typescript support](#typescript-support)
1920
- [Inline composition](#inline-composition)
@@ -133,6 +134,32 @@ const Composed = adopt({
133134
</Composed>
134135
```
135136

137+
### Mapping props from mappers
138+
139+
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.
140+
141+
```js
142+
import { adopt } from 'react-adopt'
143+
import { Value } from 'react-powerplug'
144+
145+
const mapper = {
146+
greet: <Value initial="Hi" />,
147+
name: <Value initial="John" />,
148+
}
149+
150+
const mapProps = ({ greet, name }) => ({
151+
message: `${greet.value} ${name.value}`,
152+
})
153+
154+
const Composed = adopt(mapper, mapProps)
155+
156+
const App = () => (
157+
<Composed>
158+
{({ message }) => /* ... */}
159+
</Composed>
160+
)
161+
```
162+
136163
### Leading with multiple params
137164

138165
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):

src/index.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,42 @@ test('inline composition using <Adopt> component', () => {
149149
expect(result.children().length).toBe(1)
150150
expect(result.html()).toBe('<div>foo</div>')
151151
})
152+
153+
test('mapping props as second parameter of adopt()', () => {
154+
const children = jest.fn(() => null)
155+
156+
const Composed = adopt(
157+
{
158+
foo: <Value initial="foo" />,
159+
bar: <Value initial="bar" />,
160+
},
161+
({ foo, bar }) => ({
162+
foobar: foo.value + bar.value,
163+
})
164+
)
165+
166+
mount(<Composed>{children}</Composed>)
167+
168+
expect(children).toHaveBeenCalledWith({ foobar: 'foobar' })
169+
})
170+
171+
test('mapping props as prop of <Adopt />', () => {
172+
const children = jest.fn(() => null)
173+
174+
const mapper = {
175+
foo: <Value initial="foo" />,
176+
bar: <Value initial="bar" />,
177+
}
178+
179+
const mapProps = ({ foo, bar }) => ({
180+
foobar: foo.value + bar.value,
181+
})
182+
183+
mount(
184+
<Adopt mapper={mapper} mapProps={mapProps}>
185+
{children}
186+
</Adopt>
187+
)
188+
189+
expect(children).toHaveBeenCalledWith({ foobar: 'foobar' })
190+
})

src/index.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,33 +44,45 @@ export declare type MapperValue<RP, P> =
4444

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

47-
export function adopt<RP = any, P = any>(mapper: Mapper<RP, P>): RPC<RP, P> {
47+
export declare type MapProps<RP> = (props: any) => RP
48+
49+
export function adopt<RP = any, P = any>(
50+
mapper: Mapper<RP, P>,
51+
mapProps?: MapProps<RP>
52+
): RPC<RP, P> {
4853
if (!values(mapper).some(isValidRenderProp)) {
4954
throw new Error(
5055
'The render props object mapper just accept valid elements as value'
5156
)
5257
}
5358

59+
const mapperKeys = keys(mapper)
5460
const Children: any = ({ children, ...rest }: any) =>
5561
isFn(children) && children(rest)
5662

57-
const reducer = (Component: RPC<RP>, key: string): RPC<RP> => ({
63+
const reducer = (Component: RPC<RP>, key: string, idx: number): RPC<RP> => ({
5864
children,
5965
...rest
6066
}) => (
6167
<Component {...rest}>
6268
{props => {
6369
const element = prop(key, mapper)
6470
const propsWithoutRest = omit<RP>(keys(rest), props)
71+
const isLast = idx === mapperKeys.length - 1
72+
73+
const render: ChildrenFn<RP> = cProps => {
74+
const renderProps = assign({}, propsWithoutRest, {
75+
[key]: cProps,
76+
})
6577

66-
const render: ChildrenFn<RP> = cProps =>
67-
isFn(children)
78+
return isFn(children)
6879
? children(
69-
assign({}, propsWithoutRest, {
70-
[key]: cProps,
71-
})
80+
mapProps && isFn(mapProps) && isLast
81+
? mapProps(renderProps)
82+
: renderProps
7283
)
7384
: null
85+
}
7486

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

82-
return keys(mapper).reduce(reducer, Children)
94+
return mapperKeys.reduce(reducer, Children)
8395
}
8496

8597
export type AdoptProps<RP, P> = P & {
8698
mapper: Mapper<RP, P>
8799
children: ChildrenFn<RP>
100+
mapProps?: MapProps<RP>
88101
}
89102

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

93106
constructor(props: any) {
94107
super(props)
95-
this.Composed = adopt(props.mapper)
108+
this.Composed = adopt(props.mapper, this.props.mapProps)
96109
}
97110

98111
public render(): JSX.Element {

0 commit comments

Comments
 (0)