diff --git a/content/blog/2018-02-07-update-on-async-rendering.md b/content/blog/2018-02-07-update-on-async-rendering.md index f7cfeba3..b076e7b1 100644 --- a/content/blog/2018-02-07-update-on-async-rendering.md +++ b/content/blog/2018-02-07-update-on-async-rendering.md @@ -105,8 +105,16 @@ For this reason, the recommended way to add listeners/subscriptions is to use th > > Although the pattern above is slightly more verbose, it has an added benefit of deferring the subscription creation until after the component has rendered, reducing the amount of time in the critical render path. In the near future, React may include more tools to reduce code complexity for data fetching cases like this. -You may also need to update subscriptions based on changes in `props`. In this case, you should also wait to _remove_ a subscription until `componentDidUpdate`. For example: -`embed:update-on-async-rendering/adding-event-listeners-after-continued.js` +### Updating subscriptions when `props` change + +(This is a continuation of [the example above](#adding-event-listeners-or-subscriptions).) + +You may also need to update subscriptions based on changes in `props`. In this case, you should also wait until `componentDidUpdate` before _removing_ a subscription. In the event that a render is cancelled before being committed, this will prevent us from unsubscribing prematurely. + +Howeveer, waiting to unsubscribe means that we will need to be careful about how we handle events that are dispatched in between `getDerivedStateFromProps` and `componentDidUpdate` so that we don't put stale values into the `state`. To do this, we should use the callback form of `setState` and compare the event dispatcher to the one currently in `state`. + +Here is a example: +`embed:update-on-async-rendering/updating-subscriptions-when-props-change-after.js` ### Updating `state` based on `props` diff --git a/examples/update-on-async-rendering/adding-event-listeners-after.js b/examples/update-on-async-rendering/adding-event-listeners-after.js index e6018da5..3456732c 100644 --- a/examples/update-on-async-rendering/adding-event-listeners-after.js +++ b/examples/update-on-async-rendering/adding-event-listeners-after.js @@ -31,7 +31,9 @@ class ExampleComponent extends React.Component { ); } - handleSubscriptionChange = subscribedValue => { - this.setState({subscribedValue}); + handleSubscriptionChange = dataSource => { + this.setState({ + subscribedValue: dataSource.value, + }); }; } diff --git a/examples/update-on-async-rendering/adding-event-listeners-before.js b/examples/update-on-async-rendering/adding-event-listeners-before.js index c83da8a1..ac0574db 100644 --- a/examples/update-on-async-rendering/adding-event-listeners-before.js +++ b/examples/update-on-async-rendering/adding-event-listeners-before.js @@ -18,7 +18,9 @@ class ExampleComponent extends React.Component { ); } - handleSubscriptionChange = subscribedValue => { - this.setState({subscribedValue}); + handleSubscriptionChange = dataSource => { + this.setState({ + subscribedValue: dataSource.value, + }); }; } diff --git a/examples/update-on-async-rendering/adding-event-listeners-after-continued.js b/examples/update-on-async-rendering/updating-subscriptions-when-props-change-after.js similarity index 54% rename from examples/update-on-async-rendering/adding-event-listeners-after-continued.js rename to examples/update-on-async-rendering/updating-subscriptions-when-props-change-after.js index d8516644..f9f81ed4 100644 --- a/examples/update-on-async-rendering/adding-event-listeners-after-continued.js +++ b/examples/update-on-async-rendering/updating-subscriptions-when-props-change-after.js @@ -1,17 +1,16 @@ // After class ExampleComponent extends React.Component { - // highlight-range{1-3} + // highlight-range{1-4} state = { + dataSource: this.props.dataSource, subscribedValue: this.props.dataSource.value, }; // highlight-line - // highlight-range{1-10} + // highlight-range{1-8} static getDerivedStateFromProps(nextProps, prevState) { - if ( - prevState.subscribedValue !== - nextProps.dataSource.value - ) { + if (nextProps.dataSource !== prevState.dataSource) { return { + dataSource: nextProps.dataSource, subscribedValue: nextProps.dataSource.value, }; } @@ -24,10 +23,10 @@ class ExampleComponent extends React.Component { // highlight-line // highlight-range{1-11} componentDidUpdate(prevProps, prevState) { - if (this.props.dataSource !== prevProps.dataSource) { + if (this.state.dataSource !== prevState.dataSource) { // Similar to adding subscriptions, // It's only safe to unsubscribe during the commit phase. - prevProps.dataSource.unsubscribe( + prevState.dataSource.unsubscribe( this.handleSubscriptionChange ); @@ -36,32 +35,39 @@ class ExampleComponent extends React.Component { } componentWillUnmount() { - this.props.dataSource.unsubscribe( + this.state.dataSource.unsubscribe( this.handleSubscriptionChange ); } - // highlight-range{1-18} + // highlight-range{1-14} finalizeSubscription() { // Event listeners are only safe to add during the commit phase, // So they won't leak if render is interrupted or errors. - this.props.dataSource.subscribe( + this.state.dataSource.subscribe( this.handleSubscriptionChange ); // External values could change between render and mount, // In some cases it may be important to handle this case. - if ( - this.state.subscribedValue !== - this.props.dataSource.value - ) { - this.setState({ - subscribedValue: this.props.dataSource.value, - }); + const subscribedValue = this.state.dataSource.value; + if (subscribedValue !== this.state.subscribedValue) { + this.setState({subscribedValue}); } } + // highlight-line + // highlight-range{1-13} + handleSubscriptionChange = dataSource => { + this.setState(state => { + // If this event belongs to the current data source, update. + // Otherwise we should ignore it. + if (dataSource === state.dataSource) { + return { + subscribedValue: dataSource.value, + }; + } - handleSubscriptionChange = subscribedValue => { - this.setState({subscribedValue}); + return null; + }); }; }