React Custom Hooks and useEffect infinite re-renders

Viewed 224
export const useMyCustomHook = () => {
    const uiLoad = useUiLoad(false);
    useEffect(() => {
        // ...
    }, [...]);
}

uiLoad is a custom hook (as is, obviously, useMyCustomHook). This works fine if I don't add uiLoad to the useEffect dependency array. If I do include it, it infinitely re-renders. Here's my code for the uiLoad hook:

const baseDispatch = {
    type: SET_LOADING
}

export const useUiLoad = (initial: boolean = false) => {
    const dispatch = useDispatch<Dispatch<UIAction>>();
    const loaded = useRef(false);
    useEffect(() => {
        if (initial && !loaded.current) {
            console.log('Called');
            dispatch({
                type: SET_LOADING,
                loadbarShowing: true
            });
            loaded.current= true;
        }
    }, [dispatch, initial])
    return (loading: boolean) => {
        const data: UIAction = {
            ...baseDispatch,
            loadbarShowing: loading
        }
        dispatch(data);
    }
}

As far as I know, hooks don't change on re-render. So the questions are:

  1. Why is uiLoad changing on every rerender? Using the useWhatChanged debugger tool I can find that it is, indeed, changing everytime the component re-renders. Don't hooks stay the same every render? Or does that not apply to custom hooks?

  2. Is the only solution here supressing the warning using // eslint-disable-next-line?

1 Answers

Hooks are called each time the component is rendered. The useUiLoad returns a new function each time it's called. You can return a memoized function so it's a stable reference.

export const useUiLoad = (initial: boolean = false) => {
  const dispatch = useDispatch<Dispatch<UIAction>>();
  const loaded = useRef(false);

  useEffect(() => {
    if (initial && !loaded.current) {
      console.log('Called');
      dispatch({
        type: SET_LOADING,
        loadbarShowing: true
      });
      loaded.current= true;
    }
  }, [dispatch, initial]);

  return useCallback((loading: boolean) => {
    const data: UIAction = {
      ...baseDispatch,
      loadbarShowing: loading
    }
    dispatch(data);
  }, [dispatch]); // <-- add any other dependencies if necessary
}
Related