Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save bvaughn/d569177d70b50b58bff69c3c4a5353f3 to your computer and use it in GitHub Desktop.
Save bvaughn/d569177d70b50b58bff69c3c4a5353f3 to your computer and use it in GitHub Desktop.
Advanced example for manually updating subscriptions in response to props changes in an async-safe way
// This is an advanced example! It is not typically required for application code.
// If you are using a library like Redux or MobX, use the container component provided by that library.
// If you are authoring such a library, use the technique shown below.
// This example shows how to safely update subscriptions in response to props changes.
// In this case, it is important to 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.
// We also 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`.
class ExampleComponent extends React.Component {
state = {
dataSource: this.props.dataSource,
subscribedValue: this.props.dataSource.value,
};
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.dataSource !== prevState.dataSource) {
return {
dataSource: nextProps.dataSource,
subscribedValue: nextProps.dataSource.value,
};
}
return null;
}
componentDidMount() {
this.finalizeSubscription();
}
componentDidUpdate(prevProps, prevState) {
if (this.state.dataSource !== prevState.dataSource) {
// Similar to adding subscriptions,
// It's only safe to unsubscribe during the commit phase.
prevState.dataSource.unsubscribe(
this.handleSubscriptionChange
);
this.finalizeSubscription();
}
}
componentWillUnmount() {
this.state.dataSource.unsubscribe(
this.handleSubscriptionChange
);
}
finalizeSubscription() {
// Event listeners are only safe to add during the commit phase,
// So they won't leak if render is interrupted or errors.
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.
const subscribedValue = this.state.dataSource.value;
if (subscribedValue !== this.state.subscribedValue) {
this.setState({subscribedValue});
}
}
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,
};
}
return null;
});
};
}
@johnlindquist
Copy link

@bvaughn would you mind reviewing my approach on react-streams?

https://github.com/johnlindquist/react-streams/blob/master/src/index.js#L6

Everything works fine in all my demos/examples, but your code suggests I need to return a value from the subject rather than invoking setState.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment