Subscriptions shouldn't call setState after unmount even for Promises (#12425)

This commit is contained in:
Brian Vaughn
2018-03-22 08:54:57 -07:00
committed by GitHub
parent f94a6b4fed
commit 40fa616053
2 changed files with 41 additions and 0 deletions

View File

@@ -189,6 +189,38 @@ describe('createSubscription', () => {
// Ensure that only Promise B causes an update
expect(ReactNoop.flush()).toEqual([123]);
});
it('should not call setState for a Promise that resolves after unmount', async () => {
const Subscription = createSubscription({
getCurrentValue: source => undefined,
subscribe: (source, callback) => {
source.then(value => callback(value), value => callback(value));
// (Can't unsubscribe from a Promise)
return () => {};
},
});
function render(hasLoaded) {
ReactNoop.yield('rendered');
return null;
}
let resolvePromise;
const promise = new Promise((resolve, reject) => {
resolvePromise = resolve;
});
ReactNoop.render(<Subscription source={promise}>{render}</Subscription>);
expect(ReactNoop.flush()).toEqual(['rendered']);
// Unmount
ReactNoop.render(null);
ReactNoop.flush();
// Resolve Promise should not trigger a setState warning
resolvePromise(true);
await promise;
});
});
it('should unsubscribe from old subscribables and subscribe to new subscribables when props change', () => {

View File

@@ -64,6 +64,7 @@ export function createSubscription<Property, Value>(
: undefined,
};
_hasUnmounted: boolean = false;
_unsubscribe: Unsubscribe | null = null;
static getDerivedStateFromProps(nextProps, prevState) {
@@ -93,6 +94,10 @@ export function createSubscription<Property, Value>(
componentWillUnmount() {
this.unsubscribe(this.state);
// Track mounted to avoid calling setState after unmounting
// For source like Promises that can't be unsubscribed from.
this._hasUnmounted = true;
}
render() {
@@ -103,6 +108,10 @@ export function createSubscription<Property, Value>(
const {source} = this.state;
if (source != null) {
const callback = (value: Value | void) => {
if (this._hasUnmounted) {
return;
}
this.setState(state => {
// If the value is the same, skip the unnecessary state update.
if (value === state.value) {