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; | |
}); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
@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 invokingsetState
.