diff --git a/.size-snapshot.json b/.size-snapshot.json index 2352720a..0d6593cf 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,18 +1,18 @@ { "lib/packages/recompose/dist/Recompose.umd.js": { - "bundled": 85035, - "minified": 31377, - "gzipped": 10006 + "bundled": 85956, + "minified": 31664, + "gzipped": 10060 }, "lib/packages/recompose/dist/Recompose.min.js": { - "bundled": 81529, - "minified": 30125, - "gzipped": 9618 + "bundled": 82334, + "minified": 30381, + "gzipped": 9668 }, "lib/packages/recompose/dist/Recompose.esm.js": { - "bundled": 31436, - "minified": 15712, - "gzipped": 3522, + "bundled": 32320, + "minified": 16089, + "gzipped": 3579, "treeshaked": { "rollup": 601, "webpack": 1863 diff --git a/docs/API.md b/docs/API.md index d7576847..5fc18f45 100644 --- a/docs/API.md +++ b/docs/API.md @@ -56,7 +56,8 @@ const PureComponent = pure(BaseComponent) + [`getContext()`](#getcontext) + [`lifecycle()`](#lifecycle) + [`toClass()`](#toclass) - + [`withRenderProps()`](#withrenderprops) + + [`toRenderProps()`](#torenderprops) + + [`fromRenderProps()`](#fromrenderprops) * [Static property helpers](#static-property-helpers) + [`setStatic()`](#setstatic) + [`setPropTypes()`](#setproptypes) @@ -565,10 +566,10 @@ Takes a function component and wraps it in a class. This can be used as a fallba If the base component is already a class, it returns the given component. -### `withRenderProps()` +### `toRenderProps()` ```js -withRenderProps( +toRenderProps( hoc: HigherOrderComponent ): ReactFunctionalComponent ``` @@ -578,12 +579,59 @@ Creates a component that accepts a function as a children with the high-order co Example: ```js const enhance = withProps(({ foo }) => ({ fooPlusOne: foo + 1 })) -const Enhanced = withRenderProps(enhance) +const Enhanced = toRenderProps(enhance) {({ fooPlusOne }) =>

{fooPlusOne}

}
// renders

2

``` +### `fromRenderProps()` + +```js +fromRenderProps( + RenderPropsComponent: ReactClass | ReactFunctionalComponent, + propsMapper: (props: Object) => Object, + renderPropName?: string +): HigherOrderComponent +``` + +Takes a **render props** component and a function that maps props to a new collection of props that are passed to the base component. + +The default value of third parameter (`renderPropName`) is `children`. You can use any prop (e.g., `render`) for render props component to work. + +> Check the official documents [Render Props](https://reactjs.org/docs/render-props.html#using-props-other-than-render) for more details. + +```js +import { fromRenderProps } from 'recompose'; +const { Consumer: ThemeConsumer } = React.createContext({ theme: 'dark' }); +const { Consumer: I18NConsumer } = React.createContext({ i18n: 'en' }); +const RenderPropsComponent = ({ render, value }) => render({ value: 1 }); + +const EnhancedApp = compose( + // Context (Function as Child Components) + fromRenderProps(ThemeConsumer, ({ theme }) => ({ theme })), + fromRenderProps(I18NConsumer, ({ i18n }) => ({ locale: i18n })), + // Render props + fromRenderProps(RenderPropsComponent, ({ value }) => ({ value }), 'render'), +)(App); + +// Same as +const EnhancedApp = () => ( + + {({ theme }) => ( + + {({ i18n }) => ( + ( + + )} + /> + )} + + )} + +) +``` ## Static property helpers diff --git a/src/packages/recompose/__tests__/fromRenderProps-test.js b/src/packages/recompose/__tests__/fromRenderProps-test.js new file mode 100644 index 00000000..aca3efff --- /dev/null +++ b/src/packages/recompose/__tests__/fromRenderProps-test.js @@ -0,0 +1,72 @@ +import React from 'react' +import { mount } from 'enzyme' +import { fromRenderProps, compose, toRenderProps, defaultProps } from '../' + +test('fromRenderProps passes additional props to base component', () => { + const RenderPropsComponent = ({ children }) => children({ i18n: 'zh-TW' }) + const EnhancedComponent = fromRenderProps( + RenderPropsComponent, + ({ i18n }) => ({ + i18n, + }) + )('div') + expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') + + const div = mount() + expect(div.html()).toBe(`
`) +}) + +test('fromRenderProps passes additional props to base component with custom renderPropName', () => { + const RenderPropsComponent = ({ render }) => render({ i18n: 'zh-TW' }) + const EnhancedComponent = fromRenderProps( + RenderPropsComponent, + ({ i18n }) => ({ + i18n, + }), + 'render' + )('div') + expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') + + const div = mount() + expect(div.html()).toBe(`
`) +}) + +test('fromRenderProps passes additional props to base component with 2 RenderPropsComponents', () => { + const RenderPropsComponent1 = ({ children }) => children({ theme: 'dark' }) + const RenderPropsComponent2 = ({ render }) => render({ i18n: 'zh-TW' }) + const EnhancedComponent = compose( + fromRenderProps( + RenderPropsComponent1, + ({ theme }) => ({ theme }), + 'children' + ), + fromRenderProps( + RenderPropsComponent2, + ({ i18n }) => ({ locale: i18n }), + 'render' + ) + )('div') + expect(EnhancedComponent.displayName).toBe( + 'fromRenderProps(fromRenderProps(div))' + ) + + const div = mount() + expect(div.html()).toBe(`
`) +}) + +test('fromRenderProps meet toRenderProps', () => { + const RenderPropsComponent = toRenderProps( + defaultProps({ foo1: 'bar1', foo2: 'bar2' }) + ) + + const EnhancedComponent = fromRenderProps( + RenderPropsComponent, + ({ foo1 }) => ({ + foo: foo1, + }) + )('div') + expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') + + const div = mount() + expect(div.html()).toBe(`
`) +}) diff --git a/src/packages/recompose/__tests__/withRenderProps-test.js b/src/packages/recompose/__tests__/toRenderProps-test.js similarity index 67% rename from src/packages/recompose/__tests__/withRenderProps-test.js rename to src/packages/recompose/__tests__/toRenderProps-test.js index 14c2ac3b..e8d35ceb 100644 --- a/src/packages/recompose/__tests__/withRenderProps-test.js +++ b/src/packages/recompose/__tests__/toRenderProps-test.js @@ -1,10 +1,10 @@ import React from 'react' import { mount } from 'enzyme' -import { withRenderProps, defaultProps } from '../' +import { toRenderProps, defaultProps } from '../' -test('withRenderProps creates a component from defaultProps HOC', () => { +test('toRenderProps creates a component from defaultProps HOC', () => { const enhance = defaultProps({ foo: 'bar' }) - const Enhanced = withRenderProps(enhance) + const Enhanced = toRenderProps(enhance) expect(Enhanced.displayName).toBe('defaultProps(RenderPropsComponent)') diff --git a/src/packages/recompose/__tests__/types/test_fromRenderProps.js b/src/packages/recompose/__tests__/types/test_fromRenderProps.js new file mode 100644 index 00000000..97d22901 --- /dev/null +++ b/src/packages/recompose/__tests__/types/test_fromRenderProps.js @@ -0,0 +1,43 @@ +/* @flow */ +import React from 'react' +import { compose, fromRenderProps } from '../..' + +import type { HOC } from '../..' + +const RenderPropsComponent1 = ({ children }) => children({ theme: 'dark' }) +const RenderPropsComponent2 = ({ render }) => render({ i18n: 'zh-TW' }) + +type EnhancedCompProps = {||} + +const Comp = ({ i18n, theme }) => +
+ {i18n} + {theme} + { + // $ExpectError + (i18n: number) + } + { + // $ExpectError + (theme: number) + } +
+ +const enhancer: HOC<*, EnhancedCompProps> = compose( + fromRenderProps(RenderPropsComponent1, props => ({ + theme: props.theme, + // $ExpectError property not found + err: props.iMNotExists, + })), + fromRenderProps( + RenderPropsComponent2, + props => ({ + i18n: props.i18n, + // $ExpectError property not found + err: props.iMNotExists, + }), + 'render' + ) +) + +const EnhancedComponent = enhancer(Comp) diff --git a/src/packages/recompose/__tests__/types/test_withRenderProps.js b/src/packages/recompose/__tests__/types/test_toRenderProps.js similarity index 90% rename from src/packages/recompose/__tests__/types/test_withRenderProps.js rename to src/packages/recompose/__tests__/types/test_toRenderProps.js index 2599ed14..60b30994 100644 --- a/src/packages/recompose/__tests__/types/test_withRenderProps.js +++ b/src/packages/recompose/__tests__/types/test_toRenderProps.js @@ -1,7 +1,7 @@ /* @flow */ import * as React from 'react' -import { compose, withProps, withRenderProps, withHandlers } from '../..' +import { compose, withProps, toRenderProps, withHandlers } from '../..' import type { HOC } from '../..' const enhance: HOC<*, {| +x: number |}> = compose( @@ -15,7 +15,7 @@ const enhance: HOC<*, {| +x: number |}> = compose( }) ) -const WithProps = withRenderProps(enhance) +const WithProps = toRenderProps(enhance) const Comp = () => diff --git a/src/packages/recompose/fromRenderProps.js b/src/packages/recompose/fromRenderProps.js new file mode 100644 index 00000000..6f4f6465 --- /dev/null +++ b/src/packages/recompose/fromRenderProps.js @@ -0,0 +1,28 @@ +import React from 'react' +import setDisplayName from './setDisplayName' +import wrapDisplayName from './wrapDisplayName' + +const fromRenderProps = ( + RenderPropsComponent, + propsMapper, + renderPropName = 'children' +) => BaseComponent => { + const baseFactory = React.createFactory(BaseComponent) + const renderPropsFactory = React.createFactory(RenderPropsComponent) + + const FromRenderProps = ownerProps => + renderPropsFactory({ + [renderPropName]: props => + baseFactory({ ...ownerProps, ...propsMapper(props) }), + }) + + if (process.env.NODE_ENV !== 'production') { + return setDisplayName(wrapDisplayName(BaseComponent, 'fromRenderProps'))( + FromRenderProps + ) + } + + return FromRenderProps +} + +export default fromRenderProps diff --git a/src/packages/recompose/index.js b/src/packages/recompose/index.js index 7724efea..01a4b7d9 100644 --- a/src/packages/recompose/index.js +++ b/src/packages/recompose/index.js @@ -21,7 +21,8 @@ export { default as withContext } from './withContext' export { default as getContext } from './getContext' export { default as lifecycle } from './lifecycle' export { default as toClass } from './toClass' -export { default as withRenderProps } from './withRenderProps' +export { default as toRenderProps } from './toRenderProps' +export { default as fromRenderProps } from './fromRenderProps' // Static property helpers export { default as setStatic } from './setStatic' diff --git a/src/packages/recompose/index.js.flow b/src/packages/recompose/index.js.flow index 102fbe89..344523db 100644 --- a/src/packages/recompose/index.js.flow +++ b/src/packages/recompose/index.js.flow @@ -262,9 +262,17 @@ declare export function createEventHandler(): { handler: Function, } -declare export function withRenderProps( +declare export function toRenderProps( hoc: HOC ): React$ComponentType<{| ...$Exact, children: (b: B) => React$Node, |}> + +declare export function fromRenderProps( + RenderPropsComponent: Component<{ + [RenderPropName]: (Props) => React$Node, + }>, + propsMapper: ((props: Props) => B), + renderPropName?: RenderPropName +): HOC<{ ...$Exact, ...B }, E> diff --git a/src/packages/recompose/withRenderProps.js b/src/packages/recompose/toRenderProps.js similarity index 67% rename from src/packages/recompose/withRenderProps.js rename to src/packages/recompose/toRenderProps.js index fe3a50cf..bc9f6f69 100644 --- a/src/packages/recompose/withRenderProps.js +++ b/src/packages/recompose/toRenderProps.js @@ -1,4 +1,4 @@ -export default function withRenderProps(hoc) { +export default function toRenderProps(hoc) { const RenderPropsComponent = props => props.children(props) return hoc(RenderPropsComponent) }