0

In React + TypeScript, I am trying to create a custom hook which returns stable updater function with referential types and stable signature such as those provenients from useState or useDispatch so that is not required in dependency arrays of hooks by the ESlint rule hooks/exhaustive-deps

Here is an example:

import { useRef, useState, useEffect, useCallback } from 'react';

export type SetStateWithCallback<T> = (newState: React.SetStateAction<T>, callback?: Function) => void;

export function useStateWithCallback<T>(initState: T): [T, SetStateWithCallback<T>] {
    const [state, setState] = useState<T>(initState);
    const callbackRef = useRef<Function | null>(null);
    const setStateWithCallback = useCallback((newState: React.SetStateAction<T>, callback?: Function) => {
        callbackRef.current = typeof callback === 'function' ? callback : null;
        setState(newState);
    }, []);

    useEffect(() => {
        if (callbackRef.current) {
            callbackRef.current?.(state);
            callbackRef.current = null;
        }
    }, [state]);

    return [state, setStateWithCallback];
}

But when using this custom hook, the eslint rule hooks/exhaustive-deps still requires the setStateWithCallback to be included as a dependency, differently from what it does with the built-in setState for example:

function MyTestComponent() {
    const [state, setState] = useState({});
    const [customState, setCustomState] = useStateWithCallback({});

    const test = useCallback(() => setState({}), []); // Fine. No required dependency
    const testCustom = useCallback(() => setCustomState({}), []); // Warning: React Hook useCallback has a missing dependency: 'setCustomState'. Either include it or remove the dependency array. eslint(hooks/exhaustive-deps)

    return null;
}

How can I change setCustomState to accomplish the same as setState?

1 Answer 1

0

This is a design choice in React. It's not practical to force custom functions to behave the same way as built-in hooks in terms of dependency handling.

React has deep, internal knowledge of how its built-in hooks operate, allowing the React team to optimize these hooks to prevent unnecessary re-renders or re-executions. This optimization is tailored for the common usage patterns of these hooks.

However, custom functions are different. They can be redefined on every render, especially if they're declared within a component. This redefinition can occur because custom functions often close over the component's state or props. When a function is redefined, its identity changes. If you omit such a function from the dependency list of a hook like useEffect or useCallback, React may not re-run the effect or recalculate values when necessary, leading to potential bugs or inconsistent behavior. Including custom functions in the dependency array ensures that the effect or memoized value updates correctly whenever the function or its dependent data changes.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.