mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Experimental] Add useInsertionEffect (#21913)
This commit is contained in:
@@ -78,6 +78,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
Dispatcher.useCacheRefresh();
|
||||
}
|
||||
Dispatcher.useLayoutEffect(() => {});
|
||||
Dispatcher.useInsertionEffect(() => {});
|
||||
Dispatcher.useEffect(() => {});
|
||||
Dispatcher.useImperativeHandle(undefined, () => null);
|
||||
Dispatcher.useDebugValue(null);
|
||||
@@ -191,6 +192,18 @@ function useLayoutEffect(
|
||||
});
|
||||
}
|
||||
|
||||
function useInsertionEffect(
|
||||
create: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
nextHook();
|
||||
hookLog.push({
|
||||
primitive: 'InsertionEffect',
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
});
|
||||
}
|
||||
|
||||
function useEffect(
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null,
|
||||
@@ -338,6 +351,7 @@ const Dispatcher: DispatcherType = {
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useLayoutEffect,
|
||||
useInsertionEffect,
|
||||
useMemo,
|
||||
useReducer,
|
||||
useRef,
|
||||
|
||||
+177
@@ -268,6 +268,183 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental || www
|
||||
it('should inspect the current state of all stateful hooks, including useInsertionEffect', () => {
|
||||
const useInsertionEffect = React.unstable_useInsertionEffect;
|
||||
const outsideRef = React.createRef();
|
||||
function effect() {}
|
||||
function Foo(props) {
|
||||
const [state1, setState] = React.useState('a');
|
||||
const [state2, dispatch] = React.useReducer((s, a) => a.value, 'b');
|
||||
const ref = React.useRef('c');
|
||||
|
||||
useInsertionEffect(effect);
|
||||
React.useLayoutEffect(effect);
|
||||
React.useEffect(effect);
|
||||
|
||||
React.useImperativeHandle(
|
||||
outsideRef,
|
||||
() => {
|
||||
// Return a function so that jest treats them as non-equal.
|
||||
return function Instance() {};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
React.useMemo(() => state1 + state2, [state1]);
|
||||
|
||||
function update() {
|
||||
act(() => {
|
||||
setState('A');
|
||||
});
|
||||
act(() => {
|
||||
dispatch({value: 'B'});
|
||||
});
|
||||
ref.current = 'C';
|
||||
}
|
||||
const memoizedUpdate = React.useCallback(update, []);
|
||||
return (
|
||||
<div onClick={memoizedUpdate}>
|
||||
{state1} {state2}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
});
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
|
||||
const {onClick: updateStates} = renderer.root.findByType('div').props;
|
||||
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'a',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'Reducer',
|
||||
value: 'b',
|
||||
subHooks: [],
|
||||
},
|
||||
{isStateEditable: false, id: 2, name: 'Ref', value: 'c', subHooks: []},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 3,
|
||||
name: 'InsertionEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 4,
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 5,
|
||||
name: 'Effect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 6,
|
||||
name: 'ImperativeHandle',
|
||||
value: outsideRef.current,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 7,
|
||||
name: 'Memo',
|
||||
value: 'ab',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 8,
|
||||
name: 'Callback',
|
||||
value: updateStates,
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
|
||||
updateStates();
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'A',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'Reducer',
|
||||
value: 'B',
|
||||
subHooks: [],
|
||||
},
|
||||
{isStateEditable: false, id: 2, name: 'Ref', value: 'C', subHooks: []},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 3,
|
||||
name: 'InsertionEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 4,
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 5,
|
||||
name: 'Effect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 6,
|
||||
name: 'ImperativeHandle',
|
||||
value: outsideRef.current,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 7,
|
||||
name: 'Memo',
|
||||
value: 'Ab',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 8,
|
||||
name: 'Callback',
|
||||
value: updateStates,
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect the value of the current provider in useContext', () => {
|
||||
const MyContext = React.createContext('default');
|
||||
function Foo(props) {
|
||||
|
||||
@@ -27,6 +27,7 @@ let useCallback;
|
||||
let useMemo;
|
||||
let useRef;
|
||||
let useImperativeHandle;
|
||||
let useInsertionEffect;
|
||||
let useLayoutEffect;
|
||||
let useDebugValue;
|
||||
let useOpaqueIdentifier;
|
||||
@@ -54,6 +55,7 @@ function initModules() {
|
||||
useRef = React.useRef;
|
||||
useDebugValue = React.useDebugValue;
|
||||
useImperativeHandle = React.useImperativeHandle;
|
||||
useInsertionEffect = React.unstable_useInsertionEffect;
|
||||
useLayoutEffect = React.useLayoutEffect;
|
||||
useOpaqueIdentifier = React.unstable_useOpaqueIdentifier;
|
||||
forwardRef = React.forwardRef;
|
||||
@@ -638,6 +640,22 @@ describe('ReactDOMServerHooks', () => {
|
||||
expect(domNode.textContent).toEqual('Count: 0');
|
||||
});
|
||||
});
|
||||
describe('useInsertionEffect', () => {
|
||||
// @gate experimental || www
|
||||
it('should warn when invoked during render', async () => {
|
||||
function Counter() {
|
||||
useInsertionEffect(() => {
|
||||
throw new Error('should not be invoked');
|
||||
});
|
||||
|
||||
return <Text text="Count: 0" />;
|
||||
}
|
||||
const domNode = await serverRender(<Counter />, 1);
|
||||
expect(clearYields()).toEqual(['Count: 0']);
|
||||
expect(domNode.tagName).toEqual('SPAN');
|
||||
expect(domNode.textContent).toEqual('Count: 0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useLayoutEffect', () => {
|
||||
it('should warn when invoked during render', async () => {
|
||||
|
||||
@@ -385,6 +385,22 @@ function useRef<T>(initialValue: T): {|current: T|} {
|
||||
}
|
||||
}
|
||||
|
||||
function useInsertionEffect(
|
||||
create: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
console.error(
|
||||
'useInsertionEffect does nothing on the server, because its effect cannot ' +
|
||||
"be encoded into the server renderer's output format. This will lead " +
|
||||
'to a mismatch between the initial, non-hydrated UI and the intended ' +
|
||||
'UI. To avoid this, useInsertionEffect should only be used in ' +
|
||||
'components that render exclusively on the client.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null,
|
||||
@@ -508,6 +524,7 @@ export const Dispatcher: DispatcherType = {
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
useCallback,
|
||||
// useImperativeHandle is not run in the server environment
|
||||
|
||||
@@ -136,6 +136,7 @@ import {
|
||||
NoFlags as NoHookEffect,
|
||||
HasEffect as HookHasEffect,
|
||||
Layout as HookLayout,
|
||||
Insertion as HookInsertion,
|
||||
Passive as HookPassive,
|
||||
} from './ReactHookEffectTags';
|
||||
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
|
||||
@@ -525,6 +526,8 @@ function commitHookEffectListMount(tag: HookFlags, finishedWork: Fiber) {
|
||||
let hookName;
|
||||
if ((effect.tag & HookLayout) !== NoFlags) {
|
||||
hookName = 'useLayoutEffect';
|
||||
} else if ((effect.tag & HookInsertion) !== NoFlags) {
|
||||
hookName = 'useInsertionEffect';
|
||||
} else {
|
||||
hookName = 'useEffect';
|
||||
}
|
||||
@@ -1153,7 +1156,10 @@ function commitUnmount(
|
||||
do {
|
||||
const {destroy, tag} = effect;
|
||||
if (destroy !== undefined) {
|
||||
if ((tag & HookLayout) !== NoHookEffect) {
|
||||
if (
|
||||
(tag & HookInsertion) !== NoHookEffect ||
|
||||
(tag & HookLayout) !== NoHookEffect
|
||||
) {
|
||||
if (
|
||||
enableProfilerTimer &&
|
||||
enableProfilerCommitHooks &&
|
||||
@@ -1738,6 +1744,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
commitHookEffectListUnmount(
|
||||
HookInsertion | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
|
||||
|
||||
// Layout effects are destroyed during the mutation phase so that all
|
||||
// destroy functions for all fibers are called before any create functions.
|
||||
// This prevents sibling component effects from interfering with each other,
|
||||
@@ -1810,6 +1823,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
commitHookEffectListUnmount(
|
||||
HookInsertion | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
|
||||
// Layout effects are destroyed during the mutation phase so that all
|
||||
// destroy functions for all fibers are called before any create functions.
|
||||
// This prevents sibling component effects from interfering with each other,
|
||||
|
||||
@@ -136,6 +136,7 @@ import {
|
||||
NoFlags as NoHookEffect,
|
||||
HasEffect as HookHasEffect,
|
||||
Layout as HookLayout,
|
||||
Insertion as HookInsertion,
|
||||
Passive as HookPassive,
|
||||
} from './ReactHookEffectTags';
|
||||
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
|
||||
@@ -525,6 +526,8 @@ function commitHookEffectListMount(tag: HookFlags, finishedWork: Fiber) {
|
||||
let hookName;
|
||||
if ((effect.tag & HookLayout) !== NoFlags) {
|
||||
hookName = 'useLayoutEffect';
|
||||
} else if ((effect.tag & HookInsertion) !== NoFlags) {
|
||||
hookName = 'useInsertionEffect';
|
||||
} else {
|
||||
hookName = 'useEffect';
|
||||
}
|
||||
@@ -1153,7 +1156,10 @@ function commitUnmount(
|
||||
do {
|
||||
const {destroy, tag} = effect;
|
||||
if (destroy !== undefined) {
|
||||
if ((tag & HookLayout) !== NoHookEffect) {
|
||||
if (
|
||||
(tag & HookInsertion) !== NoHookEffect ||
|
||||
(tag & HookLayout) !== NoHookEffect
|
||||
) {
|
||||
if (
|
||||
enableProfilerTimer &&
|
||||
enableProfilerCommitHooks &&
|
||||
@@ -1738,6 +1744,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
commitHookEffectListUnmount(
|
||||
HookInsertion | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
|
||||
|
||||
// Layout effects are destroyed during the mutation phase so that all
|
||||
// destroy functions for all fibers are called before any create functions.
|
||||
// This prevents sibling component effects from interfering with each other,
|
||||
@@ -1810,6 +1823,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
commitHookEffectListUnmount(
|
||||
HookInsertion | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
|
||||
// Layout effects are destroyed during the mutation phase so that all
|
||||
// destroy functions for all fibers are called before any create functions.
|
||||
// This prevents sibling component effects from interfering with each other,
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
HasEffect as HookHasEffect,
|
||||
Layout as HookLayout,
|
||||
Passive as HookPassive,
|
||||
Insertion as HookInsertion,
|
||||
} from './ReactHookEffectTags';
|
||||
import {
|
||||
getWorkInProgressRoot,
|
||||
@@ -1708,6 +1709,20 @@ function updateEffect(
|
||||
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
}
|
||||
|
||||
function mountInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
return mountEffectImpl(UpdateEffect, HookInsertion, create, deps);
|
||||
}
|
||||
|
||||
function updateInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
return updateEffectImpl(UpdateEffect, HookInsertion, create, deps);
|
||||
}
|
||||
|
||||
function mountLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2310,6 +2325,7 @@ export const ContextOnlyDispatcher: Dispatcher = {
|
||||
useContext: throwInvalidHookError,
|
||||
useEffect: throwInvalidHookError,
|
||||
useImperativeHandle: throwInvalidHookError,
|
||||
useInsertionEffect: throwInvalidHookError,
|
||||
useLayoutEffect: throwInvalidHookError,
|
||||
useMemo: throwInvalidHookError,
|
||||
useReducer: throwInvalidHookError,
|
||||
@@ -2337,6 +2353,7 @@ const HooksDispatcherOnMount: Dispatcher = {
|
||||
useEffect: mountEffect,
|
||||
useImperativeHandle: mountImperativeHandle,
|
||||
useLayoutEffect: mountLayoutEffect,
|
||||
useInsertionEffect: mountInsertionEffect,
|
||||
useMemo: mountMemo,
|
||||
useReducer: mountReducer,
|
||||
useRef: mountRef,
|
||||
@@ -2362,6 +2379,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
useContext: readContext,
|
||||
useEffect: updateEffect,
|
||||
useImperativeHandle: updateImperativeHandle,
|
||||
useInsertionEffect: updateInsertionEffect,
|
||||
useLayoutEffect: updateLayoutEffect,
|
||||
useMemo: updateMemo,
|
||||
useReducer: updateReducer,
|
||||
@@ -2388,6 +2406,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
|
||||
useContext: readContext,
|
||||
useEffect: updateEffect,
|
||||
useImperativeHandle: updateImperativeHandle,
|
||||
useInsertionEffect: updateInsertionEffect,
|
||||
useLayoutEffect: updateLayoutEffect,
|
||||
useMemo: updateMemo,
|
||||
useReducer: rerenderReducer,
|
||||
@@ -2468,6 +2487,15 @@ if (__DEV__) {
|
||||
checkDepsAreArrayDev(deps);
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
mountHookTypesDev();
|
||||
checkDepsAreArrayDev(deps);
|
||||
return mountInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2602,6 +2630,14 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
updateHookTypesDev();
|
||||
return mountInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2734,6 +2770,14 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2867,6 +2911,14 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -3004,6 +3056,15 @@ if (__DEV__) {
|
||||
mountHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -3152,6 +3213,15 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -3301,6 +3371,15 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
HasEffect as HookHasEffect,
|
||||
Layout as HookLayout,
|
||||
Passive as HookPassive,
|
||||
Insertion as HookInsertion,
|
||||
} from './ReactHookEffectTags';
|
||||
import {
|
||||
getWorkInProgressRoot,
|
||||
@@ -1708,6 +1709,20 @@ function updateEffect(
|
||||
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
}
|
||||
|
||||
function mountInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
return mountEffectImpl(UpdateEffect, HookInsertion, create, deps);
|
||||
}
|
||||
|
||||
function updateInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
return updateEffectImpl(UpdateEffect, HookInsertion, create, deps);
|
||||
}
|
||||
|
||||
function mountLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2310,6 +2325,7 @@ export const ContextOnlyDispatcher: Dispatcher = {
|
||||
useContext: throwInvalidHookError,
|
||||
useEffect: throwInvalidHookError,
|
||||
useImperativeHandle: throwInvalidHookError,
|
||||
useInsertionEffect: throwInvalidHookError,
|
||||
useLayoutEffect: throwInvalidHookError,
|
||||
useMemo: throwInvalidHookError,
|
||||
useReducer: throwInvalidHookError,
|
||||
@@ -2337,6 +2353,7 @@ const HooksDispatcherOnMount: Dispatcher = {
|
||||
useEffect: mountEffect,
|
||||
useImperativeHandle: mountImperativeHandle,
|
||||
useLayoutEffect: mountLayoutEffect,
|
||||
useInsertionEffect: mountInsertionEffect,
|
||||
useMemo: mountMemo,
|
||||
useReducer: mountReducer,
|
||||
useRef: mountRef,
|
||||
@@ -2362,6 +2379,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
useContext: readContext,
|
||||
useEffect: updateEffect,
|
||||
useImperativeHandle: updateImperativeHandle,
|
||||
useInsertionEffect: updateInsertionEffect,
|
||||
useLayoutEffect: updateLayoutEffect,
|
||||
useMemo: updateMemo,
|
||||
useReducer: updateReducer,
|
||||
@@ -2388,6 +2406,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
|
||||
useContext: readContext,
|
||||
useEffect: updateEffect,
|
||||
useImperativeHandle: updateImperativeHandle,
|
||||
useInsertionEffect: updateInsertionEffect,
|
||||
useLayoutEffect: updateLayoutEffect,
|
||||
useMemo: updateMemo,
|
||||
useReducer: rerenderReducer,
|
||||
@@ -2468,6 +2487,15 @@ if (__DEV__) {
|
||||
checkDepsAreArrayDev(deps);
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
mountHookTypesDev();
|
||||
checkDepsAreArrayDev(deps);
|
||||
return mountInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2602,6 +2630,14 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
updateHookTypesDev();
|
||||
return mountInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2734,6 +2770,14 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -2867,6 +2911,14 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -3004,6 +3056,15 @@ if (__DEV__) {
|
||||
mountHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -3152,6 +3213,15 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
@@ -3301,6 +3371,15 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useInsertionEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateInsertionEffect(create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
+5
-4
@@ -9,11 +9,12 @@
|
||||
|
||||
export type HookFlags = number;
|
||||
|
||||
export const NoFlags = /* */ 0b000;
|
||||
export const NoFlags = /* */ 0b0000;
|
||||
|
||||
// Represents whether effect should fire.
|
||||
export const HasEffect = /* */ 0b001;
|
||||
export const HasEffect = /* */ 0b0001;
|
||||
|
||||
// Represents the phase in which the effect (not the clean-up) fires.
|
||||
export const Layout = /* */ 0b010;
|
||||
export const Passive = /* */ 0b100;
|
||||
export const Insertion = /* */ 0b0010;
|
||||
export const Layout = /* */ 0b0100;
|
||||
export const Passive = /* */ 0b1000;
|
||||
|
||||
@@ -33,6 +33,7 @@ export type HookType =
|
||||
| 'useContext'
|
||||
| 'useRef'
|
||||
| 'useEffect'
|
||||
| 'useInsertionEffect'
|
||||
| 'useLayoutEffect'
|
||||
| 'useCallback'
|
||||
| 'useMemo'
|
||||
@@ -286,6 +287,10 @@ export type Dispatcher = {|
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void,
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void,
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
@@ -22,6 +22,7 @@ let Suspense;
|
||||
let useState;
|
||||
let useReducer;
|
||||
let useEffect;
|
||||
let useInsertionEffect;
|
||||
let useLayoutEffect;
|
||||
let useCallback;
|
||||
let useMemo;
|
||||
@@ -46,6 +47,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
useState = React.useState;
|
||||
useReducer = React.useReducer;
|
||||
useEffect = React.useEffect;
|
||||
useInsertionEffect = React.unstable_useInsertionEffect;
|
||||
useLayoutEffect = React.useLayoutEffect;
|
||||
useCallback = React.useCallback;
|
||||
useMemo = React.useMemo;
|
||||
@@ -2682,6 +2684,415 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useInsertionEffect', () => {
|
||||
// @gate experimental || www
|
||||
it('fires insertion effects after snapshots on update', () => {
|
||||
function CounterA(props) {
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(`Create insertion`);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(`Destroy insertion`);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
class CounterB extends React.Component {
|
||||
getSnapshotBeforeUpdate(prevProps, prevState) {
|
||||
Scheduler.unstable_yieldValue(`Get Snapshot`);
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidUpdate() {}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<CounterA />
|
||||
<CounterB />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Create insertion']);
|
||||
});
|
||||
|
||||
// Update
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<CounterA />
|
||||
<CounterB />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Get Snapshot',
|
||||
'Destroy insertion',
|
||||
'Create insertion',
|
||||
]);
|
||||
});
|
||||
|
||||
// Unmount everything
|
||||
act(() => {
|
||||
ReactNoop.render(null);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Destroy insertion']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate experimental || www
|
||||
it('fires insertion effects before layout effects', () => {
|
||||
let committedText = '(empty)';
|
||||
|
||||
function Counter(props) {
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create insertion [current: ${committedText}]`,
|
||||
);
|
||||
committedText = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy insertion [current: ${committedText}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create layout [current: ${committedText}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy layout [current: ${committedText}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
useEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create passive [current: ${committedText}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy passive [current: ${committedText}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
act(() => {
|
||||
ReactNoop.render(<Counter count={0} />);
|
||||
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'Create insertion [current: (empty)]',
|
||||
'Create layout [current: 0]',
|
||||
]);
|
||||
expect(committedText).toEqual('0');
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Create passive [current: 0]']);
|
||||
|
||||
// Unmount everything
|
||||
act(() => {
|
||||
ReactNoop.render(null);
|
||||
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'Destroy insertion [current: 0]',
|
||||
'Destroy layout [current: 0]',
|
||||
]);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Destroy passive [current: 0]']);
|
||||
});
|
||||
|
||||
// @gate experimental || www
|
||||
it('force flushes passive effects before firing new insertion effects', () => {
|
||||
let committedText = '(empty)';
|
||||
|
||||
function Counter(props) {
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create insertion [current: ${committedText}]`,
|
||||
);
|
||||
committedText = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy insertion [current: ${committedText}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create layout [current: ${committedText}]`,
|
||||
);
|
||||
committedText = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy layout [current: ${committedText}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
useEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create passive [current: ${committedText}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy passive [current: ${committedText}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(<Counter count={0} />);
|
||||
});
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'Create insertion [current: (empty)]',
|
||||
'Create layout [current: 0]',
|
||||
]);
|
||||
expect(committedText).toEqual('0');
|
||||
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(<Counter count={1} />);
|
||||
});
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'Create passive [current: 0]',
|
||||
'Destroy insertion [current: 0]',
|
||||
'Create insertion [current: 0]',
|
||||
'Destroy layout [current: 1]',
|
||||
'Create layout [current: 1]',
|
||||
]);
|
||||
expect(committedText).toEqual('1');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Destroy passive [current: 1]',
|
||||
'Create passive [current: 1]',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental || www
|
||||
it('fires all insertion effects (interleaved) before firing any layout effects', () => {
|
||||
let committedA = '(empty)';
|
||||
let committedB = '(empty)';
|
||||
|
||||
function CounterA(props) {
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Insertion 1 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
committedA = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Insertion 1 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Insertion 2 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
committedA = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Insertion 2 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Layout 1 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Layout 1 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Layout 2 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Layout 2 for Component A [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
function CounterB(props) {
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Insertion 1 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
committedB = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Insertion 1 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
useInsertionEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Insertion 2 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
committedB = props.count + '';
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Insertion 2 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Layout 1 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Layout 1 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Create Layout 2 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Destroy Layout 2 for Component B [A: ${committedA}, B: ${committedB}]`,
|
||||
);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<React.Fragment>
|
||||
<CounterA count={0} />
|
||||
<CounterB count={0} />
|
||||
</React.Fragment>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
// All insertion effects fire before all layout effects
|
||||
'Create Insertion 1 for Component A [A: (empty), B: (empty)]',
|
||||
'Create Insertion 2 for Component A [A: 0, B: (empty)]',
|
||||
'Create Insertion 1 for Component B [A: 0, B: (empty)]',
|
||||
'Create Insertion 2 for Component B [A: 0, B: 0]',
|
||||
'Create Layout 1 for Component A [A: 0, B: 0]',
|
||||
'Create Layout 2 for Component A [A: 0, B: 0]',
|
||||
'Create Layout 1 for Component B [A: 0, B: 0]',
|
||||
'Create Layout 2 for Component B [A: 0, B: 0]',
|
||||
]);
|
||||
expect([committedA, committedB]).toEqual(['0', '0']);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<React.Fragment>
|
||||
<CounterA count={1} />
|
||||
<CounterB count={1} />
|
||||
</React.Fragment>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Destroy Insertion 1 for Component A [A: 0, B: 0]',
|
||||
'Destroy Insertion 2 for Component A [A: 0, B: 0]',
|
||||
'Create Insertion 1 for Component A [A: 0, B: 0]',
|
||||
'Create Insertion 2 for Component A [A: 1, B: 0]',
|
||||
'Destroy Layout 1 for Component A [A: 1, B: 0]',
|
||||
'Destroy Layout 2 for Component A [A: 1, B: 0]',
|
||||
'Destroy Insertion 1 for Component B [A: 1, B: 0]',
|
||||
'Destroy Insertion 2 for Component B [A: 1, B: 0]',
|
||||
'Create Insertion 1 for Component B [A: 1, B: 0]',
|
||||
'Create Insertion 2 for Component B [A: 1, B: 1]',
|
||||
'Destroy Layout 1 for Component B [A: 1, B: 1]',
|
||||
'Destroy Layout 2 for Component B [A: 1, B: 1]',
|
||||
'Create Layout 1 for Component A [A: 1, B: 1]',
|
||||
'Create Layout 2 for Component A [A: 1, B: 1]',
|
||||
'Create Layout 1 for Component B [A: 1, B: 1]',
|
||||
'Create Layout 2 for Component B [A: 1, B: 1]',
|
||||
]);
|
||||
expect([committedA, committedB]).toEqual(['1', '1']);
|
||||
|
||||
// Unmount everything
|
||||
act(() => {
|
||||
ReactNoop.render(null);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Destroy Insertion 1 for Component A [A: 1, B: 1]',
|
||||
'Destroy Insertion 2 for Component A [A: 1, B: 1]',
|
||||
'Destroy Layout 1 for Component A [A: 1, B: 1]',
|
||||
'Destroy Layout 2 for Component A [A: 1, B: 1]',
|
||||
'Destroy Insertion 1 for Component B [A: 1, B: 1]',
|
||||
'Destroy Insertion 2 for Component B [A: 1, B: 1]',
|
||||
'Destroy Layout 1 for Component B [A: 1, B: 1]',
|
||||
'Destroy Layout 2 for Component B [A: 1, B: 1]',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @gate experimental || www
|
||||
it('assumes insertion effect destroy function is either a function or undefined', () => {
|
||||
function App(props) {
|
||||
useInsertionEffect(() => {
|
||||
return props.return;
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const root1 = ReactNoop.createRoot();
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root1.render(<App return={17} />);
|
||||
}),
|
||||
).toErrorDev([
|
||||
'Warning: useInsertionEffect must not return anything besides a ' +
|
||||
'function, which is used for clean-up. You returned: 17',
|
||||
]);
|
||||
|
||||
const root2 = ReactNoop.createRoot();
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root2.render(<App return={null} />);
|
||||
}),
|
||||
).toErrorDev([
|
||||
'Warning: useInsertionEffect must not return anything besides a ' +
|
||||
'function, which is used for clean-up. You returned null. If your ' +
|
||||
'effect does not require clean up, return undefined (or nothing).',
|
||||
]);
|
||||
|
||||
const root3 = ReactNoop.createRoot();
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root3.render(<App return={Promise.resolve()} />);
|
||||
}),
|
||||
).toErrorDev([
|
||||
'Warning: useInsertionEffect must not return anything besides a ' +
|
||||
'function, which is used for clean-up.\n\n' +
|
||||
'It looks like you wrote useInsertionEffect(async () => ...) or returned a Promise.',
|
||||
]);
|
||||
|
||||
// Error on unmount because React assumes the value is a function
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root3.unmount();
|
||||
}),
|
||||
).toThrow('is not a function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useLayoutEffect', () => {
|
||||
it('fires layout effects after the host has been mutated', () => {
|
||||
function getCommittedText() {
|
||||
|
||||
+1
@@ -503,6 +503,7 @@ export const Dispatcher: DispatcherType = {
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
useInsertionEffect: noop,
|
||||
useLayoutEffect,
|
||||
useCallback,
|
||||
// useImperativeHandle is not run in the server environment
|
||||
|
||||
@@ -822,6 +822,7 @@ const Dispatcher: DispatcherType = {
|
||||
useReducer: (unsupportedHook: any),
|
||||
useRef: (unsupportedHook: any),
|
||||
useState: (unsupportedHook: any),
|
||||
useInsertionEffect: (unsupportedHook: any),
|
||||
useLayoutEffect: (unsupportedHook: any),
|
||||
useImperativeHandle: (unsupportedHook: any),
|
||||
useEffect: (unsupportedHook: any),
|
||||
|
||||
@@ -35,6 +35,7 @@ export function waitForSuspense<T>(fn: () => T): Promise<T> {
|
||||
useReducer: unsupported,
|
||||
useRef: unsupported,
|
||||
useState: unsupported,
|
||||
useInsertionEffect: unsupported,
|
||||
useLayoutEffect: unsupported,
|
||||
useCallback: unsupported,
|
||||
useImperativeHandle: unsupported,
|
||||
|
||||
@@ -48,6 +48,7 @@ export {
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
unstable_useInsertionEffect,
|
||||
useMemo,
|
||||
useMutableSource,
|
||||
useMutableSource as unstable_useMutableSource,
|
||||
|
||||
@@ -42,6 +42,7 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
unstable_useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useMutableSource as unstable_useMutableSource,
|
||||
|
||||
@@ -67,6 +67,7 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
unstable_useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useMutableSource,
|
||||
|
||||
@@ -46,6 +46,7 @@ export {
|
||||
useDeferredValue as unstable_useDeferredValue, // TODO: Remove once call sights updated to useDeferredValue
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
unstable_useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useMutableSource,
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useMutableSource,
|
||||
@@ -91,6 +92,7 @@ export {
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useInsertionEffect as unstable_useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useMutableSource,
|
||||
|
||||
@@ -100,6 +100,14 @@ export function useEffect(
|
||||
return dispatcher.useEffect(create, deps);
|
||||
}
|
||||
|
||||
export function useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useInsertionEffect(create, deps);
|
||||
}
|
||||
|
||||
export function useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
Reference in New Issue
Block a user