[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.
This commit is contained in:
Sebastian Markbåge
2025-01-08 12:08:30 -05:00
committed by GitHub
parent 379089d288
commit e30c6693e4
6 changed files with 0 additions and 316 deletions

View File

@@ -12,7 +12,6 @@
let act;
let React;
let ReactDOM;
let ReactDOMClient;
let assertConsoleErrorDev;
let assertConsoleWarnDev;
@@ -63,24 +62,6 @@ const POST_WILL_UNMOUNT_STATE = {
hasWillUnmountCompleted: true,
};
/**
* Every React component is in one of these life cycles.
*/
type ComponentLifeCycle =
/**
* Mounted components have a DOM node representation and are capable of
* receiving new props.
*/
| 'MOUNTED'
/**
* Unmounted components are inactive and cannot receive new props.
*/
| 'UNMOUNTED';
function getLifeCycleState(instance): ComponentLifeCycle {
return instance.updater.isMounted(instance) ? 'MOUNTED' : 'UNMOUNTED';
}
/**
* TODO: We should make any setState calls fail in
* `getInitialState` and `componentWillMount`. They will usually fail
@@ -99,7 +80,6 @@ describe('ReactComponentLifeCycle', () => {
} = require('internal-test-utils'));
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
});
@@ -290,137 +270,6 @@ describe('ReactComponentLifeCycle', () => {
});
});
it('should correctly determine if a component is mounted', async () => {
class Component extends React.Component {
_isMounted() {
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
return this.updater.isMounted(this);
}
UNSAFE_componentWillMount() {
expect(this._isMounted()).toBeFalsy();
}
componentDidMount() {
expect(this._isMounted()).toBeTruthy();
}
render() {
expect(this._isMounted()).toBeFalsy();
return <div />;
}
}
let instance;
const element = <Component ref={current => (instance = current)} />;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(element);
});
assertConsoleErrorDev([
'Component is accessing isMounted inside its render() function. ' +
'render() should be a pure function of props and state. ' +
'It should never access something that requires stale data ' +
'from the previous render, such as refs. ' +
'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
' in Component (at **)',
]);
expect(instance._isMounted()).toBeTruthy();
});
it('should correctly determine if a null component is mounted', async () => {
class Component extends React.Component {
_isMounted() {
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
return this.updater.isMounted(this);
}
UNSAFE_componentWillMount() {
expect(this._isMounted()).toBeFalsy();
}
componentDidMount() {
expect(this._isMounted()).toBeTruthy();
}
render() {
expect(this._isMounted()).toBeFalsy();
return null;
}
}
let instance;
const element = <Component ref={current => (instance = current)} />;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(element);
});
assertConsoleErrorDev([
'Component is accessing isMounted inside its render() function. ' +
'render() should be a pure function of props and state. ' +
'It should never access something that requires stale data ' +
'from the previous render, such as refs. ' +
'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
' in Component (at **)',
]);
expect(instance._isMounted()).toBeTruthy();
});
it('isMounted should return false when unmounted', async () => {
class Component extends React.Component {
render() {
return <div />;
}
}
const root = ReactDOMClient.createRoot(document.createElement('div'));
const instanceRef = React.createRef();
await act(() => {
root.render(<Component ref={instanceRef} />);
});
const instance = instanceRef.current;
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
expect(instance.updater.isMounted(instance)).toBe(true);
await act(() => {
root.unmount();
});
expect(instance.updater.isMounted(instance)).toBe(false);
});
// @gate www && classic
it('warns if legacy findDOMNode is used inside render', async () => {
class Component extends React.Component {
state = {isMounted: false};
componentDidMount() {
this.setState({isMounted: true});
}
render() {
if (this.state.isMounted) {
expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
}
return <div />;
}
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Component />);
});
assertConsoleErrorDev([
'Component is accessing findDOMNode inside its render(). ' +
'render() should be a pure function of props and state. ' +
'It should never access something that requires stale data ' +
'from the previous render, such as refs. ' +
'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
' in Component (at **)',
]);
});
it('should carry through each of the phases of setup', async () => {
class LifeCycleComponent extends React.Component {
constructor(props, context) {
@@ -433,20 +282,16 @@ describe('ReactComponentLifeCycle', () => {
hasWillUnmountCompleted: false,
};
this._testJournal.returnedFromGetInitialState = clone(initState);
this._testJournal.lifeCycleAtStartOfGetInitialState =
getLifeCycleState(this);
this.state = initState;
}
UNSAFE_componentWillMount() {
this._testJournal.stateAtStartOfWillMount = clone(this.state);
this._testJournal.lifeCycleAtStartOfWillMount = getLifeCycleState(this);
this.state.hasWillMountCompleted = true;
}
componentDidMount() {
this._testJournal.stateAtStartOfDidMount = clone(this.state);
this._testJournal.lifeCycleAtStartOfDidMount = getLifeCycleState(this);
this.setState({hasDidMountCompleted: true});
}
@@ -454,10 +299,8 @@ describe('ReactComponentLifeCycle', () => {
const isInitialRender = !this.state.hasRenderCompleted;
if (isInitialRender) {
this._testJournal.stateInInitialRender = clone(this.state);
this._testJournal.lifeCycleInInitialRender = getLifeCycleState(this);
} else {
this._testJournal.stateInLaterRender = clone(this.state);
this._testJournal.lifeCycleInLaterRender = getLifeCycleState(this);
}
// you would *NEVER* do anything like this in real code!
this.state.hasRenderCompleted = true;
@@ -466,8 +309,6 @@ describe('ReactComponentLifeCycle', () => {
componentWillUnmount() {
this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
this._testJournal.lifeCycleAtStartOfWillUnmount =
getLifeCycleState(this);
this.state.hasWillUnmountCompleted = true;
}
}
@@ -480,52 +321,33 @@ describe('ReactComponentLifeCycle', () => {
await act(() => {
root.render(<LifeCycleComponent ref={instanceRef} />);
});
assertConsoleErrorDev([
'LifeCycleComponent is accessing isMounted inside its render() function. ' +
'render() should be a pure function of props and state. ' +
'It should never access something that requires stale data ' +
'from the previous render, such as refs. ' +
'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
' in LifeCycleComponent (at **)',
]);
const instance = instanceRef.current;
// getInitialState
expect(instance._testJournal.returnedFromGetInitialState).toEqual(
GET_INIT_STATE_RETURN_VAL,
);
expect(instance._testJournal.lifeCycleAtStartOfGetInitialState).toBe(
'UNMOUNTED',
);
// componentWillMount
expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
instance._testJournal.returnedFromGetInitialState,
);
expect(instance._testJournal.lifeCycleAtStartOfWillMount).toBe('UNMOUNTED');
// componentDidMount
expect(instance._testJournal.stateAtStartOfDidMount).toEqual(
DID_MOUNT_STATE,
);
expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe('MOUNTED');
// initial render
expect(instance._testJournal.stateInInitialRender).toEqual(
INIT_RENDER_STATE,
);
expect(instance._testJournal.lifeCycleInInitialRender).toBe('UNMOUNTED');
expect(getLifeCycleState(instance)).toBe('MOUNTED');
// Now *update the component*
instance.forceUpdate();
// render 2nd time
expect(instance._testJournal.stateInLaterRender).toEqual(NEXT_RENDER_STATE);
expect(instance._testJournal.lifeCycleInLaterRender).toBe('MOUNTED');
expect(getLifeCycleState(instance)).toBe('MOUNTED');
await act(() => {
root.unmount();
@@ -535,10 +357,8 @@ describe('ReactComponentLifeCycle', () => {
WILL_UNMOUNT_STATE,
);
// componentWillUnmount called right before unmount.
expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe('MOUNTED');
// But the current lifecycle of the component is unmounted.
expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
});

View File

@@ -23,7 +23,6 @@ import {
disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import {isMounted} from './ReactFiberTreeReflection';
import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
import shallowEqual from 'shared/shallowEqual';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -165,7 +164,6 @@ function applyDerivedStateFromProps(
}
const classComponentUpdater = {
isMounted,
// $FlowFixMe[missing-local-annot]
enqueueSetState(inst: any, payload: any, callback) {
const fiber = getInstance(inst);

View File

@@ -10,7 +10,6 @@
import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack';
import {isFiberMounted} from './ReactFiberTreeReflection';
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {ClassComponent, HostRoot} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -285,13 +284,6 @@ function findCurrentUnmaskedContext(fiber: Fiber): Object {
} else {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
throw new Error(
'Expected subtree parent to be a mounted class component. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
let node: Fiber = fiber;
do {
switch (node.tag) {

View File

@@ -11,10 +11,7 @@ import type {Fiber} from './ReactInternalTypes';
import type {Container, SuspenseInstance} from './ReactFiberConfig';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import {get as getInstance} from 'shared/ReactInstanceMap';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {
ClassComponent,
HostComponent,
HostHoistable,
HostSingleton,
@@ -24,7 +21,6 @@ import {
SuspenseComponent,
} from './ReactWorkTags';
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
import {current as currentOwner, isRendering} from './ReactCurrentFiber';
export function getNearestMountedFiber(fiber: Fiber): null | Fiber {
let node = fiber;
@@ -83,37 +79,6 @@ export function getContainerFromFiber(fiber: Fiber): null | Container {
: null;
}
export function isFiberMounted(fiber: Fiber): boolean {
return getNearestMountedFiber(fiber) === fiber;
}
export function isMounted(component: React$Component<any, any>): boolean {
if (__DEV__) {
const owner = currentOwner;
if (owner !== null && isRendering && owner.tag === ClassComponent) {
const ownerFiber: Fiber = owner;
const instance = ownerFiber.stateNode;
if (!instance._warnedAboutRefsInRender) {
console.error(
'%s is accessing isMounted inside its render() function. ' +
'render() should be a pure function of props and state. It should ' +
'never access something that requires stale data from the previous ' +
'render, such as refs. Move this logic to componentDidMount and ' +
'componentDidUpdate instead.',
getComponentNameFromFiber(ownerFiber) || 'A component',
);
}
instance._warnedAboutRefsInRender = true;
}
}
const fiber: ?Fiber = getInstance(component);
if (!fiber) {
return false;
}
return getNearestMountedFiber(fiber) === fiber;
}
function assertIsMounted(fiber: Fiber) {
if (getNearestMountedFiber(fiber) !== fiber) {
throw new Error('Unable to find node on an unmounted component.');

View File

@@ -40,94 +40,6 @@ describe('ReactIncrementalReflection', () => {
return {type: 'span', children: [], prop, hidden: false};
}
it('handles isMounted even when the initial render is deferred', async () => {
const instances = [];
class Component extends React.Component {
_isMounted() {
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
return this.updater.isMounted(this);
}
UNSAFE_componentWillMount() {
instances.push(this);
Scheduler.log('componentWillMount: ' + this._isMounted());
}
componentDidMount() {
Scheduler.log('componentDidMount: ' + this._isMounted());
}
render() {
return <span />;
}
}
function Foo() {
return <Component />;
}
React.startTransition(() => {
ReactNoop.render(<Foo />);
});
// Render part way through but don't yet commit the updates.
await waitFor(['componentWillMount: false']);
expect(instances[0]._isMounted()).toBe(false);
// Render the rest and commit the updates.
await waitForAll(['componentDidMount: true']);
expect(instances[0]._isMounted()).toBe(true);
});
it('handles isMounted when an unmount is deferred', async () => {
const instances = [];
class Component extends React.Component {
_isMounted() {
return this.updater.isMounted(this);
}
UNSAFE_componentWillMount() {
instances.push(this);
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount: ' + this._isMounted());
}
render() {
Scheduler.log('Component');
return <span />;
}
}
function Other() {
Scheduler.log('Other');
return <span />;
}
function Foo(props) {
return props.mount ? <Component /> : <Other />;
}
ReactNoop.render(<Foo mount={true} />);
await waitForAll(['Component']);
expect(instances[0]._isMounted()).toBe(true);
React.startTransition(() => {
ReactNoop.render(<Foo mount={false} />);
});
// Render part way through but don't yet commit the updates so it is not
// fully unmounted yet.
await waitFor(['Other']);
expect(instances[0]._isMounted()).toBe(true);
// Finish flushing the unmount.
await waitForAll(['componentWillUnmount: true']);
expect(instances[0]._isMounted()).toBe(false);
});
it('finds no node before insertion and correct node before deletion', async () => {
let classInstance = null;

View File

@@ -108,9 +108,6 @@ type InternalInstance = {
};
const classComponentUpdater = {
isMounted(inst: any) {
return false;
},
// $FlowFixMe[missing-local-annot]
enqueueSetState(inst: any, payload: any, callback) {
const internals: InternalInstance = getInstance(inst);