Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

commented Apr 8, 2018

@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
You can’t perform that action at this time.