@samitouri / QOS-React-2 / commits / e30c6693e4

[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);