Skip to content

Commit

Permalink
[Experimental] Add back useMutationEffect
Browse files Browse the repository at this point in the history
  • Loading branch information
rickhanlonii committed Jul 20, 2021
1 parent 9b76d2d commit a10210c
Show file tree
Hide file tree
Showing 21 changed files with 812 additions and 6 deletions.
14 changes: 14 additions & 0 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
Dispatcher.useCacheRefresh();
}
Dispatcher.useLayoutEffect(() => {});
Dispatcher.useMutationEffect(() => {});
Dispatcher.useEffect(() => {});
Dispatcher.useImperativeHandle(undefined, () => null);
Dispatcher.useDebugValue(null);
Expand Down Expand Up @@ -191,6 +192,18 @@ function useLayoutEffect(
});
}

function useMutationEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
nextHook();
hookLog.push({
primitive: 'MutationEffect',
stackError: new Error(),
value: create,
});
}

function useEffect(
create: () => (() => void) | void,
inputs: Array<mixed> | void | null,
Expand Down Expand Up @@ -320,6 +333,7 @@ const Dispatcher: DispatcherType = {
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMutationEffect,
useMemo,
useReducer,
useRef,
Expand Down
107 changes: 107 additions & 0 deletions packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,113 @@ describe('ReactHooksInspection', () => {
]);
});

// @gate experimental || www
// @gate source // TODO: Build gets confused by the unstable prefix.
it('should inspect a tree of multiple levels of hooks, including useMutationEffect', () => {
function effect() {}
function useCustom(value) {
const [state] = React.useReducer((s, a) => s, value);
React.useEffect(effect);
return state;
}
function useBar(value) {
const result = useCustom(value);
React.useLayoutEffect(effect);
return result;
}
function useBaz(value) {
React.unstable_useMutationEffect(effect);
const result = useCustom(value);
return result;
}
function Foo(props) {
const value1 = useBar('hello');
const value2 = useBaz('world');
return (
<div>
{value1} {value2}
</div>
);
}
const tree = ReactDebugTools.inspectHooks(Foo, {});
expect(tree).toEqual([
{
isStateEditable: false,
id: null,
name: 'Bar',
value: undefined,
subHooks: [
{
isStateEditable: false,
id: null,
name: 'Custom',
value: undefined,
subHooks: [
{
isStateEditable: true,
id: 0,
name: 'Reducer',
value: 'hello',
subHooks: [],
},
{
isStateEditable: false,
id: 1,
name: 'Effect',
value: effect,
subHooks: [],
},
],
},
{
isStateEditable: false,
id: 2,
name: 'LayoutEffect',
value: effect,
subHooks: [],
},
],
},
{
isStateEditable: false,
id: null,
name: 'Baz',
value: undefined,
subHooks: [
{
isStateEditable: false,
id: 3,
name: 'MutationEffect',
value: effect,
subHooks: [],
},
{
isStateEditable: false,
id: null,
name: 'Custom',
subHooks: [
{
isStateEditable: true,
id: 4,
name: 'Reducer',
subHooks: [],
value: 'world',
},
{
isStateEditable: false,
id: 5,
name: 'Effect',
subHooks: [],
value: effect,
},
],
value: undefined,
},
],
},
]);
});

it('should inspect the default value using the useContext hook', () => {
const MyContext = React.createContext('default');
function Foo(props) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,183 @@ describe('ReactHooksInspectionIntegration', () => {
]);
});

// @gate experimental || www
// @gate source // TODO: Build gets confused by the unstable prefix.
it('should inspect the current state of all stateful hooks, including useMutationEffect', () => {
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');

React.unstable_useMutationEffect(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: 'MutationEffect',
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: 'MutationEffect',
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let useCallback;
let useMemo;
let useRef;
let useImperativeHandle;
let useMutationEffect;
let useLayoutEffect;
let useDebugValue;
let useOpaqueIdentifier;
Expand Down Expand Up @@ -54,6 +55,7 @@ function initModules() {
useRef = React.useRef;
useDebugValue = React.useDebugValue;
useImperativeHandle = React.useImperativeHandle;
useMutationEffect = React.unstable_useMutationEffect;
useLayoutEffect = React.useLayoutEffect;
useOpaqueIdentifier = React.unstable_useOpaqueIdentifier;
forwardRef = React.forwardRef;
Expand Down Expand Up @@ -638,6 +640,22 @@ describe('ReactDOMServerHooks', () => {
expect(domNode.textContent).toEqual('Count: 0');
});
});
describe('useMutationEffect', () => {
// @gate experimental || www
it('should warn when invoked during render', async () => {
function Counter() {
useMutationEffect(() => {
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 () => {
Expand Down
Loading

0 comments on commit a10210c

Please sign in to comment.