[Fiber] Delete isMounted internals (#31966)
The public API has been deleted a long time ago so this should be unused unless it's used by hacks. It should be replaced with an effect/lifecycle that manually tracks this if you need it. The problem with this API is how the timing implemented because it requires Placement/Hydration flags to be cleared too early. In fact, that's why we also have a separate PlacementDEV flag that works differently. https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberCommitWork.js#L2157-L2165 We should be able to remove this code now.
Sebastian Markbåge committed
Jan 8, 2025 at 12:08 UTC
e30c6693e4c7f2aec25b07f5df69a87163dbee81
6 files changed
-316
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
-180
@@ -12,7 +12,6 @@
12
let act;
13
14
let React;
15
-let ReactDOM;
15
let ReactDOMClient;
16
let assertConsoleErrorDev;
17
let assertConsoleWarnDev;
@@ -63,24 +62,6 @@ const POST_WILL_UNMOUNT_STATE = {
62
hasWillUnmountCompleted: true,
63
};
64
66
-/**
67
- * Every React component is in one of these life cycles.
68
- */
69
-type ComponentLifeCycle =
70
- /**
71
- * Mounted components have a DOM node representation and are capable of
72
- * receiving new props.
73
- */
74
- | 'MOUNTED'
75
- /**
76
- * Unmounted components are inactive and cannot receive new props.
77
- */
78
- | 'UNMOUNTED';
79
-
80
-function getLifeCycleState(instance): ComponentLifeCycle {
81
- return instance.updater.isMounted(instance) ? 'MOUNTED' : 'UNMOUNTED';
82
-}
83
-
65
/**
66
* TODO: We should make any setState calls fail in
67
* `getInitialState` and `componentWillMount`. They will usually fail
@@ -99,7 +80,6 @@ describe('ReactComponentLifeCycle', () => {
80
} = require('internal-test-utils'));
81
82
React = require('react');
102
- ReactDOM = require('react-dom');
83
ReactDOMClient = require('react-dom/client');
84
});
85
@@ -290,137 +270,6 @@ describe('ReactComponentLifeCycle', () => {
270
});
271
});
272
293
- it('should correctly determine if a component is mounted', async () => {
294
- class Component extends React.Component {
295
- _isMounted() {
296
- // No longer a public API, but we can test that it works internally by
297
- // reaching into the updater.
298
- return this.updater.isMounted(this);
299
- }
300
- UNSAFE_componentWillMount() {
301
- expect(this._isMounted()).toBeFalsy();
302
- }
303
- componentDidMount() {
304
- expect(this._isMounted()).toBeTruthy();
305
- }
306
- render() {
307
- expect(this._isMounted()).toBeFalsy();
308
- return <div />;
309
- }
310
- }
311
-
312
- let instance;
313
- const element = <Component ref={current => (instance = current)} />;
314
-
315
- const container = document.createElement('div');
316
- const root = ReactDOMClient.createRoot(container);
317
- await act(() => {
318
- root.render(element);
319
- });
320
- assertConsoleErrorDev([
321
- 'Component is accessing isMounted inside its render() function. ' +
322
- 'render() should be a pure function of props and state. ' +
323
- 'It should never access something that requires stale data ' +
324
- 'from the previous render, such as refs. ' +
325
- 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
326
- ' in Component (at **)',
327
- ]);
328
- expect(instance._isMounted()).toBeTruthy();
329
- });
330
-
331
- it('should correctly determine if a null component is mounted', async () => {
332
- class Component extends React.Component {
333
- _isMounted() {
334
- // No longer a public API, but we can test that it works internally by
335
- // reaching into the updater.
336
- return this.updater.isMounted(this);
337
- }
338
- UNSAFE_componentWillMount() {
339
- expect(this._isMounted()).toBeFalsy();
340
- }
341
- componentDidMount() {
342
- expect(this._isMounted()).toBeTruthy();
343
- }
344
- render() {
345
- expect(this._isMounted()).toBeFalsy();
346
- return null;
347
- }
348
- }
349
-
350
- let instance;
351
- const element = <Component ref={current => (instance = current)} />;
352
-
353
- const container = document.createElement('div');
354
- const root = ReactDOMClient.createRoot(container);
355
- await act(() => {
356
- root.render(element);
357
- });
358
- assertConsoleErrorDev([
359
- 'Component is accessing isMounted inside its render() function. ' +
360
- 'render() should be a pure function of props and state. ' +
361
- 'It should never access something that requires stale data ' +
362
- 'from the previous render, such as refs. ' +
363
- 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
364
- ' in Component (at **)',
365
- ]);
366
- expect(instance._isMounted()).toBeTruthy();
367
- });
368
-
369
- it('isMounted should return false when unmounted', async () => {
370
- class Component extends React.Component {
371
- render() {
372
- return <div />;
373
- }
374
- }
375
-
376
- const root = ReactDOMClient.createRoot(document.createElement('div'));
377
- const instanceRef = React.createRef();
378
- await act(() => {
379
- root.render(<Component ref={instanceRef} />);
380
- });
381
- const instance = instanceRef.current;
382
-
383
- // No longer a public API, but we can test that it works internally by
384
- // reaching into the updater.
385
- expect(instance.updater.isMounted(instance)).toBe(true);
386
-
387
- await act(() => {
388
- root.unmount();
389
- });
390
-
391
- expect(instance.updater.isMounted(instance)).toBe(false);
392
- });
393
-
394
- // @gate www && classic
395
- it('warns if legacy findDOMNode is used inside render', async () => {
396
- class Component extends React.Component {
397
- state = {isMounted: false};
398
- componentDidMount() {
399
- this.setState({isMounted: true});
400
- }
401
- render() {
402
- if (this.state.isMounted) {
403
- expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
404
- }
405
- return <div />;
406
- }
407
- }
408
-
409
- const container = document.createElement('div');
410
- const root = ReactDOMClient.createRoot(container);
411
- await act(() => {
412
- root.render(<Component />);
413
- });
414
- assertConsoleErrorDev([
415
- 'Component is accessing findDOMNode inside its render(). ' +
416
- 'render() should be a pure function of props and state. ' +
417
- 'It should never access something that requires stale data ' +
418
- 'from the previous render, such as refs. ' +
419
- 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
420
- ' in Component (at **)',
421
- ]);
422
- });
423
-
273
it('should carry through each of the phases of setup', async () => {
274
class LifeCycleComponent extends React.Component {
275
constructor(props, context) {
@@ -433,20 +282,16 @@ describe('ReactComponentLifeCycle', () => {
282
hasWillUnmountCompleted: false,
283
};
284
this._testJournal.returnedFromGetInitialState = clone(initState);
436
- this._testJournal.lifeCycleAtStartOfGetInitialState =
437
- getLifeCycleState(this);
285
this.state = initState;
286
}
287
288
UNSAFE_componentWillMount() {
289
this._testJournal.stateAtStartOfWillMount = clone(this.state);
443
- this._testJournal.lifeCycleAtStartOfWillMount = getLifeCycleState(this);
290
this.state.hasWillMountCompleted = true;
291
}
292
293
componentDidMount() {
294
this._testJournal.stateAtStartOfDidMount = clone(this.state);
449
- this._testJournal.lifeCycleAtStartOfDidMount = getLifeCycleState(this);
295
this.setState({hasDidMountCompleted: true});
296
}
297
@@ -454,10 +299,8 @@ describe('ReactComponentLifeCycle', () => {
299
const isInitialRender = !this.state.hasRenderCompleted;
300
if (isInitialRender) {
301
this._testJournal.stateInInitialRender = clone(this.state);
457
- this._testJournal.lifeCycleInInitialRender = getLifeCycleState(this);
302
} else {
303
this._testJournal.stateInLaterRender = clone(this.state);
460
- this._testJournal.lifeCycleInLaterRender = getLifeCycleState(this);
304
}
305
// you would *NEVER* do anything like this in real code!
306
this.state.hasRenderCompleted = true;
@@ -466,8 +309,6 @@ describe('ReactComponentLifeCycle', () => {
309
310
componentWillUnmount() {
311
this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
469
- this._testJournal.lifeCycleAtStartOfWillUnmount =
470
- getLifeCycleState(this);
312
this.state.hasWillUnmountCompleted = true;
313
}
314
}
@@ -480,52 +321,33 @@ describe('ReactComponentLifeCycle', () => {
321
await act(() => {
322
root.render(<LifeCycleComponent ref={instanceRef} />);
323
});
483
- assertConsoleErrorDev([
484
- 'LifeCycleComponent is accessing isMounted inside its render() function. ' +
485
- 'render() should be a pure function of props and state. ' +
486
- 'It should never access something that requires stale data ' +
487
- 'from the previous render, such as refs. ' +
488
- 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
489
- ' in LifeCycleComponent (at **)',
490
- ]);
324
const instance = instanceRef.current;
325
326
// getInitialState
327
expect(instance._testJournal.returnedFromGetInitialState).toEqual(
328
GET_INIT_STATE_RETURN_VAL,
329
);
497
- expect(instance._testJournal.lifeCycleAtStartOfGetInitialState).toBe(
498
- 'UNMOUNTED',
499
- );
330
331
// componentWillMount
332
expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
333
instance._testJournal.returnedFromGetInitialState,
334
);
505
- expect(instance._testJournal.lifeCycleAtStartOfWillMount).toBe('UNMOUNTED');
335
336
// componentDidMount
337
expect(instance._testJournal.stateAtStartOfDidMount).toEqual(
338
DID_MOUNT_STATE,
339
);
511
- expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe('MOUNTED');
340
341
// initial render
342
expect(instance._testJournal.stateInInitialRender).toEqual(
343
INIT_RENDER_STATE,
344
);
517
- expect(instance._testJournal.lifeCycleInInitialRender).toBe('UNMOUNTED');
518
-
519
- expect(getLifeCycleState(instance)).toBe('MOUNTED');
345
346
// Now *update the component*
347
instance.forceUpdate();
348
349
// render 2nd time
350
expect(instance._testJournal.stateInLaterRender).toEqual(NEXT_RENDER_STATE);
526
- expect(instance._testJournal.lifeCycleInLaterRender).toBe('MOUNTED');
527
-
528
- expect(getLifeCycleState(instance)).toBe('MOUNTED');
351
352
await act(() => {
353
root.unmount();
@@ -535,10 +357,8 @@ describe('ReactComponentLifeCycle', () => {
357
WILL_UNMOUNT_STATE,
358
);
359
// componentWillUnmount called right before unmount.
538
- expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe('MOUNTED');
360
361
// But the current lifecycle of the component is unmounted.
541
- expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
362
expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
363
});
364
packages/react-reconciler/src/ReactFiberClassComponent.js
-2
@@ -23,7 +23,6 @@ import {
23
disableDefaultPropsExceptForClasses,
24
} from 'shared/ReactFeatureFlags';
25
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
26
-import {isMounted} from './ReactFiberTreeReflection';
26
import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
27
import shallowEqual from 'shared/shallowEqual';
28
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -165,7 +164,6 @@ function applyDerivedStateFromProps(
164
}
165
166
const classComponentUpdater = {
168
- isMounted,
167
// $FlowFixMe[missing-local-annot]
168
enqueueSetState(inst: any, payload: any, callback) {
169
const fiber = getInstance(inst);
packages/react-reconciler/src/ReactFiberContext.js
-8
@@ -10,7 +10,6 @@
10
import type {Fiber} from './ReactInternalTypes';
11
import type {StackCursor} from './ReactFiberStack';
12
13
-import {isFiberMounted} from './ReactFiberTreeReflection';
13
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
14
import {ClassComponent, HostRoot} from './ReactWorkTags';
15
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -285,13 +284,6 @@ function findCurrentUnmaskedContext(fiber: Fiber): Object {
284
} else {
285
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
286
// makes sense elsewhere
288
- if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
289
- throw new Error(
290
- 'Expected subtree parent to be a mounted class component. ' +
291
- 'This error is likely caused by a bug in React. Please file an issue.',
292
- );
293
- }
294
-
287
let node: Fiber = fiber;
288
do {
289
switch (node.tag) {
packages/react-reconciler/src/ReactFiberTreeReflection.js
-35
@@ -11,10 +11,7 @@ import type {Fiber} from './ReactInternalTypes';
11
import type {Container, SuspenseInstance} from './ReactFiberConfig';
12
import type {SuspenseState} from './ReactFiberSuspenseComponent';
13
14
-import {get as getInstance} from 'shared/ReactInstanceMap';
15
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
14
import {
17
- ClassComponent,
15
HostComponent,
16
HostHoistable,
17
HostSingleton,
@@ -24,7 +21,6 @@ import {
21
SuspenseComponent,
22
} from './ReactWorkTags';
23
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
27
-import {current as currentOwner, isRendering} from './ReactCurrentFiber';
24
25
export function getNearestMountedFiber(fiber: Fiber): null | Fiber {
26
let node = fiber;
@@ -83,37 +79,6 @@ export function getContainerFromFiber(fiber: Fiber): null | Container {
79
: null;
80
}
81
86
-export function isFiberMounted(fiber: Fiber): boolean {
87
- return getNearestMountedFiber(fiber) === fiber;
88
-}
89
-
90
-export function isMounted(component: React$Component<any, any>): boolean {
91
- if (__DEV__) {
92
- const owner = currentOwner;
93
- if (owner !== null && isRendering && owner.tag === ClassComponent) {
94
- const ownerFiber: Fiber = owner;
95
- const instance = ownerFiber.stateNode;
96
- if (!instance._warnedAboutRefsInRender) {
97
- console.error(
98
- '%s is accessing isMounted inside its render() function. ' +
99
- 'render() should be a pure function of props and state. It should ' +
100
- 'never access something that requires stale data from the previous ' +
101
- 'render, such as refs. Move this logic to componentDidMount and ' +
102
- 'componentDidUpdate instead.',
103
- getComponentNameFromFiber(ownerFiber) || 'A component',
104
- );
105
- }
106
- instance._warnedAboutRefsInRender = true;
107
- }
108
- }
109
-
110
- const fiber: ?Fiber = getInstance(component);
111
- if (!fiber) {
112
- return false;
113
- }
114
- return getNearestMountedFiber(fiber) === fiber;
115
-}
116
-
82
function assertIsMounted(fiber: Fiber) {
83
if (getNearestMountedFiber(fiber) !== fiber) {
84
throw new Error('Unable to find node on an unmounted component.');
packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.js
-88
@@ -40,94 +40,6 @@ describe('ReactIncrementalReflection', () => {
40
return {type: 'span', children: [], prop, hidden: false};
41
}
42
43
- it('handles isMounted even when the initial render is deferred', async () => {
44
- const instances = [];
45
-
46
- class Component extends React.Component {
47
- _isMounted() {
48
- // No longer a public API, but we can test that it works internally by
49
- // reaching into the updater.
50
- return this.updater.isMounted(this);
51
- }
52
- UNSAFE_componentWillMount() {
53
- instances.push(this);
54
- Scheduler.log('componentWillMount: ' + this._isMounted());
55
- }
56
- componentDidMount() {
57
- Scheduler.log('componentDidMount: ' + this._isMounted());
58
- }
59
- render() {
60
- return <span />;
61
- }
62
- }
63
-
64
- function Foo() {
65
- return <Component />;
66
- }
67
-
68
- React.startTransition(() => {
69
- ReactNoop.render(<Foo />);
70
- });
71
-
72
- // Render part way through but don't yet commit the updates.
73
- await waitFor(['componentWillMount: false']);
74
-
75
- expect(instances[0]._isMounted()).toBe(false);
76
-
77
- // Render the rest and commit the updates.
78
- await waitForAll(['componentDidMount: true']);
79
-
80
- expect(instances[0]._isMounted()).toBe(true);
81
- });
82
-
83
- it('handles isMounted when an unmount is deferred', async () => {
84
- const instances = [];
85
-
86
- class Component extends React.Component {
87
- _isMounted() {
88
- return this.updater.isMounted(this);
89
- }
90
- UNSAFE_componentWillMount() {
91
- instances.push(this);
92
- }
93
- componentWillUnmount() {
94
- Scheduler.log('componentWillUnmount: ' + this._isMounted());
95
- }
96
- render() {
97
- Scheduler.log('Component');
98
- return <span />;
99
- }
100
- }
101
-
102
- function Other() {
103
- Scheduler.log('Other');
104
- return <span />;
105
- }
106
-
107
- function Foo(props) {
108
- return props.mount ? <Component /> : <Other />;
109
- }
110
-
111
- ReactNoop.render(<Foo mount={true} />);
112
- await waitForAll(['Component']);
113
-
114
- expect(instances[0]._isMounted()).toBe(true);
115
-
116
- React.startTransition(() => {
117
- ReactNoop.render(<Foo mount={false} />);
118
- });
119
- // Render part way through but don't yet commit the updates so it is not
120
- // fully unmounted yet.
121
- await waitFor(['Other']);
122
-
123
- expect(instances[0]._isMounted()).toBe(true);
124
-
125
- // Finish flushing the unmount.
126
- await waitForAll(['componentWillUnmount: true']);
127
-
128
- expect(instances[0]._isMounted()).toBe(false);
129
- });
130
-
43
it('finds no node before insertion and correct node before deletion', async () => {
44
let classInstance = null;
45
packages/react-server/src/ReactFizzClassComponent.js
-3
@@ -108,9 +108,6 @@ type InternalInstance = {
108
};
109
110
const classComponentUpdater = {
111
- isMounted(inst: any) {
112
- return false;
113
- },
111
// $FlowFixMe[missing-local-annot]
112
enqueueSetState(inst: any, payload: any, callback) {
113
const internals: InternalInstance = getInstance(inst);