diff --git a/README.md b/README.md index 899aa21..85735ef 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ export default () => ( #### middleware ```js -import {middleware} from 'fusion-react-async'; +import { middleware } from 'fusion-react-async'; ``` A middleware that adds a `PrepareProvider` to the React tree. @@ -116,62 +116,63 @@ Consider using [`fusion-react`](https://github.com/fusionjs/fusion-react) instea #### split ```js -import {split} from 'fusion-react-async'; +import { split } from 'fusion-react-async'; -const Component = split({load, LoadingComponent, ErrorComponent}); +const Component = split({ load, LoadingComponent, ErrorComponent }); ``` -- `load: () => Promise` - Required. Load a component asynchronously. Typically, this should make a dynamic `import()` call. +* `load: () => Promise` - Required. Load a component asynchronously. Typically, this should make a dynamic `import()` call. The Fusion compiler takes care of bundling the appropriate code and de-duplicating dependencies. The argument to `import` should be a string literal (not a variable). See [webpack docs](https://webpack.js.org/api/module-methods/#import-) for more information. -- `LoadingComponent` - Required. A component to be displayed while the asynchronous component hasn't downloaded -- `ErrorComponent` - Required. A component to be displayed if the asynchronous component could not be loaded -- `Component` - A placeholder component that can be used in your view which will show the asynchronous component +* `LoadingComponent` - Required. A component to be displayed while the asynchronous component hasn't downloaded +* `ErrorComponent` - Required. A component to be displayed if the asynchronous component could not be loaded +* `Component` - A placeholder component that can be used in your view which will show the asynchronous component #### prepare ```js -import {prepare} from 'fusion-react-async'; +import { prepare } from 'fusion-react-async'; -const Component = prepare(element) +const Component = prepare(element); ``` -- `Element: React.Element` - Required. A React element created via `React.createElement` -- `Component: React.Component` - A React component +* `Element: React.Element` - Required. A React element created via `React.createElement` +* `Component: React.Component` - A React component Consider using [`fusion-react`](https://github.com/fusionjs/fusion-react) instead of setting up React manually and calling `prepare` directly, since that package does all of that for you. The `prepare` function recursively traverses the element rendering tree and awaits the side effects of components decorated with `prepared` (or `dispatched`). -It should be used (and `await`-ed) *before* calling `renderToString` on the server. If any of the side effects throws, `prepare` will also throw. +It should be used (and `await`-ed) _before_ calling `renderToString` on the server. If any of the side effects throws, `prepare` will also throw. #### prepared ```js -import {prepared} from 'fusion-react-async'; +import { prepared } from 'fusion-react-async'; const hoc = prepared(sideEffect, opts); ``` -- `sideEffect: : (props: Object, context: Object) => Promise` - Required. when `prepare` is called, `sideEffect` is called (and awaited) before continuing the rendering traversal. -- `opts: {defer, boundary, componentDidMount, componentWillReceiveProps, forceUpdate, contextTypes}` - Optional - - `defer: boolean` - Optional. Defaults to `true`. If the component is deferred, skip the prepare step - - `boundary: boolean` - Optional. Defaults to `false`. Stop traversing if the component is defer or boundary - - `componentDidMount: boolean` - Optional. Defaults to `true`. On the browser, `sideEffect` is called when the component is mounted. - - `componentWillReceiveProps: boolean` - Optional. Defaults to `false`. On the browser, `sideEffect` is called again whenever the component receive props. - - `forceUpdate: boolean` - Optional. Defaults to `false`. - - `contextTypes: Object` - Optional. Custom React context types to add to the prepared component. -- `hoc: (Component: React.Component) => React.Component` - A higher-order component that returns a component that awaits for async side effects before rendering - - `Component: React.Component` - Required. +* `sideEffect: : (props: Object, context: Object) => Promise` - Required. when `prepare` is called, `sideEffect` is called (and awaited) before continuing the rendering traversal. +* `opts: {defer, boundary, componentDidMount, componentWillReceiveProps, forceUpdate, contextTypes}` - Optional + * `defer: boolean` - Optional. Defaults to `true`. If the component is deferred, skip the prepare step + * `boundary: boolean` - Optional. Defaults to `false`. Stop traversing if the component is defer or boundary + * `componentDidMount: boolean` - Optional. Defaults to `true`. On the browser, `sideEffect` is called when the component is mounted. + * [TO BE DEPRECATED] `componentWillReceiveProps: boolean` - Optional. Defaults to `false`. On the browser, `sideEffect` is called again whenever the component receive props. + * `componentDidUpdate: boolean` - Optional. Defaults to `false`. On the browser, `sideEffect` is called again right after updating occurs. + * `forceUpdate: boolean` - Optional. Defaults to `false`. + * `contextTypes: Object` - Optional. Custom React context types to add to the prepared component. +* `hoc: (Component: React.Component) => React.Component` - A higher-order component that returns a component that awaits for async side effects before rendering + * `Component: React.Component` - Required. #### exclude ```js -import {exclude} from 'fusion-react-async'; +import { exclude } from 'fusion-react-async'; const NewComponent = exclude(Component); ``` -- `Component: React.Component` - Required. A component that should not be traversed via `prepare`. -- `NewComponent: React.Component` - A component that is excluded from `prepare` traversal. +* `Component: React.Component` - Required. A component that should not be traversed via `prepare`. +* `NewComponent: React.Component` - A component that is excluded from `prepare` traversal. Stops `prepare` traversal at `Component`. Useful for optimizing the `prepare` traversal to visit the minimum number of nodes. diff --git a/src/__tests__/__node__/prepare-render.node.js b/src/__tests__/__node__/prepare-render.node.js index a5aa595..046073c 100644 --- a/src/__tests__/__node__/prepare-render.node.js +++ b/src/__tests__/__node__/prepare-render.node.js @@ -387,6 +387,62 @@ tape('Preparing an async app with componentWillReceiveProps option', t => { }); }); +tape('Preparing an async app with componentDidUpdate option', t => { + let numConstructors = 0; + let numRenders = 0; + let numChildRenders = 0; + let numPrepares = 0; + class SimpleComponent extends Component { + constructor(props, context) { + super(props, context); + t.equal( + context.__IS_PREPARE__, + true, + 'sets __IS_PREPARE__ to true in context' + ); + numConstructors++; + } + render() { + numRenders++; + return ; + } + } + function SimplePresentational() { + numChildRenders++; + return
Hello World
; + } + const AsyncParent = prepared( + props => { + numPrepares++; + t.equal( + props.data, + 'test', + 'passes props through to prepared component correctly' + ); + return Promise.resolve(); + }, + { + componentDidUpdate: true, + } + )(SimpleComponent); + const app = ; + const p = prepare(app); + t.ok(p instanceof Promise, 'prepare returns a promise'); + p.then(() => { + t.equal(numPrepares, 1, 'runs the prepare function once'); + t.equal(numConstructors, 1, 'constructs SimpleComponent once'); + t.equal(numRenders, 1, 'renders SimpleComponent once'); + t.equal(numChildRenders, 1, 'renders SimplePresentational once'); + // triggers componentDidMount + const wrapper = shallow(app); + t.equal(numPrepares, 2, 'runs prepare on componentDidMount'); + // triggers componentDidUpdate + wrapper.setProps({test: true}); + t.equal(numPrepares, 3, 'runs prepare on componentDidUpdate'); + t.end(); + }); +}); + tape('Preparing a Fragment', t => { const app = ( diff --git a/src/prepared.js b/src/prepared.js index 7c2ee06..6f4f662 100644 --- a/src/prepared.js +++ b/src/prepared.js @@ -15,6 +15,7 @@ const prepared = (prepare, opts = {}) => OriginalComponent => { defer: false, componentDidMount: true, componentWillReceiveProps: false, + componentDidUpdate: false, contextTypes: {}, forceUpdate: false, }, @@ -45,6 +46,12 @@ const prepared = (prepare, opts = {}) => OriginalComponent => { } } + componentDidUpdate() { + if (opts.componentDidUpdate) { + prepare(this.props, this.context); + } + } + render() { return ; } diff --git a/src/traverse-exclude.js b/src/traverse-exclude.js index a1a301a..67a753a 100644 --- a/src/traverse-exclude.js +++ b/src/traverse-exclude.js @@ -11,5 +11,6 @@ import prepared from './prepared.js'; export default prepared(Promise.resolve(), { componentDidMount: false, componentWillReceiveProps: false, + componentDidUpdate: false, defer: true, });