diff --git a/packages/create-subscription/src/__tests__/createSubscription-test.internal.js b/packages/create-subscription/src/__tests__/createSubscription-test.internal.js index 8dee9bfa5c..2cc81b696d 100644 --- a/packages/create-subscription/src/__tests__/createSubscription-test.internal.js +++ b/packages/create-subscription/src/__tests__/createSubscription-test.internal.js @@ -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({render}); + 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', () => { diff --git a/packages/create-subscription/src/createSubscription.js b/packages/create-subscription/src/createSubscription.js index 699db33781..df18f8b3c7 100644 --- a/packages/create-subscription/src/createSubscription.js +++ b/packages/create-subscription/src/createSubscription.js @@ -64,6 +64,7 @@ export function createSubscription( : undefined, }; + _hasUnmounted: boolean = false; _unsubscribe: Unsubscribe | null = null; static getDerivedStateFromProps(nextProps, prevState) { @@ -93,6 +94,10 @@ export function createSubscription( 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( 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) {