diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js
index a616ff395f..37315929e4 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js
@@ -24,7 +24,7 @@ let useContext;
let useCallback;
let useMemo;
let useRef;
-let useAPI;
+let useImperativeMethods;
let useMutationEffect;
let useLayoutEffect;
let forwardRef;
@@ -49,7 +49,7 @@ function initModules() {
useCallback = React.useCallback;
useMemo = React.useMemo;
useRef = React.useRef;
- useAPI = React.useAPI;
+ useImperativeMethods = React.useImperativeMethods;
useMutationEffect = React.useMutationEffect;
useLayoutEffect = React.useLayoutEffect;
forwardRef = React.forwardRef;
@@ -518,10 +518,10 @@ describe('ReactDOMServerHooks', () => {
});
});
- describe('useAPI', () => {
+ describe('useImperativeMethods', () => {
it('should not be invoked on the server', async () => {
function Counter(props, ref) {
- useAPI(ref, () => {
+ useImperativeMethods(ref, () => {
throw new Error('should not be invoked');
});
return ;
diff --git a/packages/react-dom/src/server/ReactPartialRendererHooks.js b/packages/react-dom/src/server/ReactPartialRendererHooks.js
index 1bebbc7444..b859e6dfca 100644
--- a/packages/react-dom/src/server/ReactPartialRendererHooks.js
+++ b/packages/react-dom/src/server/ReactPartialRendererHooks.js
@@ -365,8 +365,8 @@ export const Dispatcher = {
useState,
useMutationEffect,
useLayoutEffect,
- // useAPI is not run in the server environment
- useAPI: noop,
+ // useImperativeMethods is not run in the server environment
+ useImperativeMethods: noop,
// Callbacks are not run in the server environment.
useCallback: noop,
// Effects are not run in the server environment.
diff --git a/packages/react-reconciler/src/ReactFiberDispatcher.js b/packages/react-reconciler/src/ReactFiberDispatcher.js
index 10c0dee649..b5f27df334 100644
--- a/packages/react-reconciler/src/ReactFiberDispatcher.js
+++ b/packages/react-reconciler/src/ReactFiberDispatcher.js
@@ -9,10 +9,10 @@
import {readContext} from './ReactFiberNewContext';
import {
- useAPI,
useCallback,
useContext,
useEffect,
+ useImperativeMethods,
useLayoutEffect,
useMemo,
useMutationEffect,
@@ -23,10 +23,10 @@ import {
export const Dispatcher = {
readContext,
- useAPI,
useCallback,
useContext,
useEffect,
+ useImperativeMethods,
useLayoutEffect,
useMemo,
useMutationEffect,
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js
index eef5c602b0..35ad699d20 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.js
@@ -179,9 +179,7 @@ export function finishHooks(
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
- if (componentUpdateQueue !== null) {
- renderedWork.updateQueue = (componentUpdateQueue: any);
- }
+ renderedWork.updateQueue = (componentUpdateQueue: any);
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
@@ -576,26 +574,27 @@ function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
- let nextEffect;
let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
+ let destroy = null;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
- const prevInputs = prevEffect.inputs;
- nextEffect = pushEffect(
- inputsAreEqual(nextInputs, prevInputs) ? NoHookEffect : hookEffectTag,
- create,
- prevEffect.destroy,
- nextInputs,
- );
- } else {
- nextEffect = pushEffect(hookEffectTag, create, null, nextInputs);
+ destroy = prevEffect.destroy;
+ if (inputsAreEqual(nextInputs, prevEffect.inputs)) {
+ pushEffect(NoHookEffect, create, destroy, nextInputs);
+ return;
+ }
}
- workInProgressHook.memoizedState = nextEffect;
currentlyRenderingFiber.effectTag |= fiberEffectTag;
+ workInProgressHook.memoizedState = pushEffect(
+ hookEffectTag,
+ create,
+ destroy,
+ nextInputs,
+ );
}
-export function useAPI(
+export function useImperativeMethods(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array | void | null,
diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
index 9ee5ef633c..c6844256d6 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
@@ -24,7 +24,7 @@ let useLayoutEffect;
let useCallback;
let useMemo;
let useRef;
-let useAPI;
+let useImperativeMethods;
let forwardRef;
let flushPassiveEffects;
let memo;
@@ -70,7 +70,7 @@ describe('ReactHooks', () => {
useCallback = React.useCallback;
useMemo = React.useMemo;
useRef = React.useRef;
- useAPI = React.useAPI;
+ useImperativeMethods = React.useImperativeMethods;
forwardRef = React.forwardRef;
memo = React.memo;
});
@@ -87,7 +87,7 @@ describe('ReactHooks', () => {
it('resumes after an interruption', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
- useAPI(ref, () => ({updateCount}));
+ useImperativeMethods(ref, () => ({updateCount}));
return ;
}
Counter = forwardRef(Counter);
@@ -171,7 +171,7 @@ describe('ReactHooks', () => {
it('simple mount and update', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
- useAPI(ref, () => ({updateCount}));
+ useImperativeMethods(ref, () => ({updateCount}));
return ;
}
Counter = forwardRef(Counter);
@@ -195,7 +195,7 @@ describe('ReactHooks', () => {
ReactNoop.yield('getInitialState');
return props.initialState;
});
- useAPI(ref, () => ({updateCount}));
+ useImperativeMethods(ref, () => ({updateCount}));
return ;
}
Counter = forwardRef(Counter);
@@ -213,7 +213,7 @@ describe('ReactHooks', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
const [label, updateLabel] = useState('Count');
- useAPI(ref, () => ({updateCount, updateLabel}));
+ useImperativeMethods(ref, () => ({updateCount, updateLabel}));
return ;
}
Counter = forwardRef(Counter);
@@ -232,7 +232,7 @@ describe('ReactHooks', () => {
it('callbacks', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
- useAPI(ref, () => ({updateCount}));
+ useImperativeMethods(ref, () => ({updateCount}));
return ;
}
Counter = forwardRef(Counter);
@@ -264,7 +264,7 @@ describe('ReactHooks', () => {
it('does not fire callbacks more than once when rebasing', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
- useAPI(ref, () => ({updateCount}));
+ useImperativeMethods(ref, () => ({updateCount}));
return ;
}
Counter = forwardRef(Counter);
@@ -507,7 +507,7 @@ describe('ReactHooks', () => {
function Counter({row: newRow}, ref) {
let [reducer, setReducer] = useState(() => reducerA);
let [count, dispatch] = useReducer(reducer, 0);
- useAPI(ref, () => ({dispatch}));
+ useImperativeMethods(ref, () => ({dispatch}));
if (count < 20) {
dispatch('increment');
// Swap reducers each time we increment
@@ -568,7 +568,7 @@ describe('ReactHooks', () => {
function Counter(props, ref) {
const [count, dispatch] = useReducer(reducer, 0);
- useAPI(ref, () => ({dispatch}));
+ useImperativeMethods(ref, () => ({dispatch}));
return ;
}
Counter = forwardRef(Counter);
@@ -609,7 +609,7 @@ describe('ReactHooks', () => {
function Counter(props, ref) {
const [count, dispatch] = useReducer(reducer, 0, initialAction);
- useAPI(ref, () => ({dispatch}));
+ useImperativeMethods(ref, () => ({dispatch}));
return ;
}
Counter = forwardRef(Counter);
@@ -910,7 +910,6 @@ describe('ReactHooks', () => {
expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);
- flushPassiveEffects();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).toHaveBeenCalledTimes(0);
});
@@ -1019,6 +1018,33 @@ describe('ReactHooks', () => {
expect(ReactNoop.getChildren()).toEqual([]);
});
+ it('unmounts on deletion after skipped effect', () => {
+ function Counter(props) {
+ useEffect(() => {
+ ReactNoop.yield(`Did create [${props.count}]`);
+ return () => {
+ ReactNoop.yield(`Did destroy [${props.count}]`);
+ };
+ }, []);
+ return ;
+ }
+ ReactNoop.render();
+ expect(ReactNoop.flush()).toEqual(['Count: 0']);
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
+ flushPassiveEffects();
+ expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);
+
+ ReactNoop.render();
+ expect(ReactNoop.flush()).toEqual(['Count: 1']);
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
+ flushPassiveEffects();
+ expect(ReactNoop.clearYields()).toEqual(null);
+
+ ReactNoop.render(null);
+ expect(ReactNoop.flush()).toEqual(['Did destroy [0]']);
+ expect(ReactNoop.getChildren()).toEqual([]);
+ });
+
it('skips effect if constructor has not changed', () => {
function effect() {
ReactNoop.yield(`Did mount`);
diff --git a/packages/react/src/React.js b/packages/react/src/React.js
index b2bbf2e8d2..04f3592ced 100644
--- a/packages/react/src/React.js
+++ b/packages/react/src/React.js
@@ -29,10 +29,10 @@ import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import memo from './memo';
import {
- useAPI,
useCallback,
useContext,
useEffect,
+ useImperativeMethods,
useLayoutEffect,
useMemo,
useMutationEffect,
@@ -89,10 +89,10 @@ if (enableStableConcurrentModeAPIs) {
}
if (enableHooks) {
- React.useAPI = useAPI;
React.useCallback = useCallback;
React.useContext = useContext;
React.useEffect = useEffect;
+ React.useImperativeMethods = useImperativeMethods;
React.useLayoutEffect = useLayoutEffect;
React.useMemo = useMemo;
React.useMutationEffect = useMutationEffect;
diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js
index 1cc8b79ac5..0e8f46dddb 100644
--- a/packages/react/src/ReactHooks.js
+++ b/packages/react/src/ReactHooks.js
@@ -110,11 +110,11 @@ export function useMemo(
return dispatcher.useMemo(create, inputs);
}
-export function useAPI(
+export function useImperativeMethods(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array | void | null,
): void {
const dispatcher = resolveDispatcher();
- return dispatcher.useAPI(ref, create, inputs);
+ return dispatcher.useImperativeMethods(ref, create, inputs);
}