mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[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:
committed by
GitHub
parent
379089d288
commit
e30c6693e4
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.');
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user