Last active
March 27, 2022 09:29
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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
@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
.