Skip to content
This repository has been archived by the owner on Aug 1, 2018. It is now read-only.

Commit

Permalink
Add opts.componentDidUpdate
Browse files Browse the repository at this point in the history
  • Loading branch information
koulmomo authored and fusion-bot[bot] committed Apr 9, 2018
1 parent 741f905 commit 82259aa
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 26 deletions.
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
56 changes: 56 additions & 0 deletions src/__tests__/__node__/prepare-render.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <SimplePresentational />;
}
}
function SimplePresentational() {
numChildRenders++;
return <div>Hello World</div>;
}
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 = <AsyncParent data="test" />;
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 = (
<React.Fragment>
Expand Down
7 changes: 7 additions & 0 deletions src/prepared.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const prepared = (prepare, opts = {}) => OriginalComponent => {
defer: false,
componentDidMount: true,
componentWillReceiveProps: false,
componentDidUpdate: false,
contextTypes: {},
forceUpdate: false,
},
Expand Down Expand Up @@ -45,6 +46,12 @@ const prepared = (prepare, opts = {}) => OriginalComponent => {
}
}

componentDidUpdate() {
if (opts.componentDidUpdate) {
prepare(this.props, this.context);
}
}

render() {
return <OriginalComponent {...this.props} />;
}
Expand Down
1 change: 1 addition & 0 deletions src/traverse-exclude.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ import prepared from './prepared.js';
export default prepared(Promise.resolve(), {
componentDidMount: false,
componentWillReceiveProps: false,
componentDidUpdate: false,
defer: true,
});

0 comments on commit 82259aa

Please sign in to comment.