Skip to content

Commit 326f25f

Browse files
committed
feat: add support to pass a component type as mapper value
1 parent 31e1bc7 commit 326f25f

File tree

4 files changed

+92
-37
lines changed

4 files changed

+92
-37
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
"compose"
3535
],
3636
"dependencies": {
37-
"react": "^16.3.2"
37+
"hoist-non-react-statics": "^2.5.0",
38+
"react": "^16.3.2",
39+
"react-display-name": "^0.2.4"
3840
},
3941
"peerDependencies": {
4042
"react": ">= 0.14.0"

src/index.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,33 @@ test('mapping props as prop of <Adopt />', () => {
201201

202202
expect(children).toHaveBeenCalledWith({ foobar: 'foobar' })
203203
})
204+
205+
test('hoisting non-static react methods from mapper values', () => {
206+
interface GreeterProps {
207+
name: string
208+
render?: (name: string) => JSX.Element
209+
}
210+
211+
class Greeter extends React.Component<GreeterProps> {
212+
public static sayHello = (name: string): string => `Hello ${name}`
213+
214+
public render(): any {
215+
const { render } = this.props
216+
return render && typeof render === 'function' && render(`Hello John`)
217+
}
218+
}
219+
220+
const getHelloFromStatic = jest.fn((value: string) => value)
221+
const children = jest.fn(() => null)
222+
223+
const Composed: any = adopt({
224+
name: ({ render }) => render('John'),
225+
greeter: Greeter,
226+
})
227+
228+
mount(<Composed>{children}</Composed>)
229+
getHelloFromStatic(Composed.sayHello('John'))
230+
231+
expect(getHelloFromStatic).toHaveBeenCalledWith('Hello John')
232+
expect(children).toHaveBeenCalledWith({ greeter: 'Hello John', name: 'John' })
233+
})

src/index.tsx

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react'
22
import { ReactNode, ReactElement } from 'react'
3+
import hoistNonReactStatic from 'hoist-non-react-statics'
4+
import getDisplayName from 'react-display-name'
35

46
const { values, keys, assign } = Object
57

@@ -25,14 +27,14 @@ const isFn = (val: any): boolean => Boolean(val) && typeof val === 'function'
2527
const isValidRenderProp = (prop: ReactNode | ChildrenFn<any>): boolean =>
2628
React.isValidElement(prop) || isFn(prop)
2729

28-
export declare type RPC<RP, P = {}> = React.SFC<
30+
export declare type RPC<RP, P = {}> = React.ComponentType<
2931
P & {
3032
children?: ChildrenFn<RP>
3133
render?: ChildrenFn<RP>
3234
}
3335
>
3436

35-
export declare type MapperComponent<RP, P> = React.SFC<
37+
export declare type MapperComponent<RP, P> = React.ComponentType<
3638
RP &
3739
P & {
3840
render?: ChildrenFn<any>
@@ -63,40 +65,53 @@ export function adopt<RP = any, P = any>(
6365
? render(rest)
6466
: children && isFn(children) && children(rest)
6567

66-
const reducer = (Component: RPC<RP>, key: string, idx: number): RPC<RP> => ({
67-
render: pRender,
68-
children,
69-
...rest
70-
}) => (
71-
<Component {...rest}>
72-
{props => {
73-
const element = prop(key, mapper)
74-
const propsWithoutRest = omit<RP>(keys(rest), props)
75-
const isLast = idx === mapperKeys.length - 1
76-
const render = pRender && isFn(pRender) ? pRender : children
77-
78-
const renderFn: ChildrenFn<RP> = cProps => {
79-
const renderProps = assign({}, propsWithoutRest, {
80-
[key]: cProps,
81-
})
82-
83-
const propsToPass =
84-
mapProps && isFn(mapProps) && isLast
85-
? mapProps(renderProps)
86-
: renderProps
87-
88-
return render && isFn(render) ? render(propsToPass) : null
89-
}
90-
91-
return isFn(element)
92-
? React.createElement(
93-
element,
94-
assign({}, rest, props, { render: renderFn })
95-
)
96-
: React.cloneElement(element, {}, renderFn)
97-
}}
98-
</Component>
99-
)
68+
Children.displayName = 'Adopt'
69+
70+
const reducer = (Component: RPC<RP>, key: string, idx: number): RPC<RP> => {
71+
const element = prop(key, mapper)
72+
const displayName = getDisplayName(Component)
73+
const nextDisplayName = getDisplayName(element)
74+
const isLast = idx === mapperKeys.length - 1
75+
76+
const NewComponent: RPC<RP> = ({
77+
render: pRender,
78+
children,
79+
...rest
80+
}: any) => (
81+
<Component {...rest}>
82+
{props => {
83+
const propsWithoutRest = omit<RP>(keys(rest), props)
84+
const render = pRender && isFn(pRender) ? pRender : children
85+
86+
const renderFn: ChildrenFn<RP> = cProps => {
87+
const renderProps = assign({}, propsWithoutRest, {
88+
[key]: cProps,
89+
})
90+
91+
const propsToPass =
92+
mapProps && isFn(mapProps) && isLast
93+
? mapProps(renderProps)
94+
: renderProps
95+
96+
return render && isFn(render) ? render(propsToPass) : null
97+
}
98+
99+
return isFn(element)
100+
? React.createElement(
101+
element,
102+
assign({}, rest, props, { render: renderFn })
103+
)
104+
: React.cloneElement(element, {}, renderFn)
105+
}}
106+
</Component>
107+
)
108+
109+
NewComponent.displayName = `${displayName}(${nextDisplayName})`
110+
111+
return isFn(element)
112+
? hoistNonReactStatic(NewComponent, element)
113+
: NewComponent
114+
}
100115

101116
return mapperKeys.reduce(reducer, Children)
102117
}

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,10 @@ [email protected]:
22142214
version "4.2.1"
22152215
resolved "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
22162216

2217+
hoist-non-react-statics@^2.5.0:
2218+
version "2.5.0"
2219+
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
2220+
22172221
home-or-tmp@^2.0.0:
22182222
version "2.0.0"
22192223
resolved "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -4288,6 +4292,10 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
42884292
minimist "^1.2.0"
42894293
strip-json-comments "~2.0.1"
42904294

4295+
react-display-name@^0.2.4:
4296+
version "0.2.4"
4297+
resolved "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.4.tgz#e2a670b81d79a2204335510c01246f4c92ff12cf"
4298+
42914299
react-dom@^16.3.2:
42924300
version "16.3.2"
42934301
resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"

0 commit comments

Comments
 (0)