Testing Custom Hook and useEffect Not Firing

Viewed 2819

I am testing a small custom hook and I can't get the useEffect inside of it to fire. I have the hook in a mounted component and have tested other custom hooks that do not have a useEffect with this method successfully. The ref gets set to false as expected (and breaks as expected when set to true), but it never updates to true when useEffect should have ran. The code works when ran outside of a test.

My hook:

export default function useIsComponentMounted() {
    const isMountedRef = useRef(false);
    useEffect(() => {
        isMountedRef.current = true;
        return function cleanup() {
            isMountedRef.current = false;
        };
    }, []);
    return isMountedRef.current;
}

My method of mounting:

function TestHook(props) {
    const { callback } = props;
    callback();
    return <div />;
}

export const testHook = callback => mount(<TestHook callback={callback} />);

My Test:

describe('useIsComponentMounted', () => {
    it('should keep track of if a component is mounted', () => {
        let expected = true;
        let mounted;
        const component = testHook(() => {
            mounted = useIsComponentMounted();
        });
        expect(mounted).toBe(expected);
        component.unmount();
        expected = false;
        expect(mounted).toBe(expected);
    });
});
1 Answers

Here's what I think is happening:

useEffect takes place after render, so when you expect mounted to be true, useEffect has not yet run. Updating a ref does not trigger a re-render, so your component never updates.

The way I got this to work is by forcing an update, by calling component.setProps() before each expectation:

    it('should keep track of if a component is mounted', () => {
        let mounted;
        const component = testHook(() => {
            mounted = useIsComponentMounted();
        });
        component.setProps(); // Feels a bit hacky, but it forces a re-render
        expect(mounted).toBe(true);
        component.unmount();
        component.setProps(); // This apparently even works after unmount!
        expect(mounted).toBe(false);
    });

This diagram is helpful to show when render, DOM updates, useEffect, useLayoutEffect, and paints happen: https://raw.githubusercontent.com/donavon/hook-flow/master/hook-flow.png

Related