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

Add opts.componentDidUpdate #86

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
});