Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks: activable states patterns

I'm trying to create a component that can activate/disable some functionality depending on the props. All of those functions will need a state that has to be managed.

I was trying to think to a possible pattern that would permit us not to create the state in case the connected functionality is not active. I thought to 2 possible way to do that:

WAY #1

function useFunctionality(initState, enable) {
    if (!enable) {
        return null;
    }

    const [funct, updateFunct] = useState(init);
    return funct;
}

function Component({ enableFunct }) {
    const funct = useFunctionality('test', enableFunct);
    return (...);
}

WAY #2

function useFunctionality(initState, enable) {
    const [funct, updateFunct] = useState(init);
    return enable ? funct : null;
}

function Component({ enableFunct }) {
    const funct = useFunctionality('test', enableFunct);
    return (...);
}

In both ways, hooks keep working. The question is: which way do you feel more correct? There is a better way to do this?

like image 676
Roberto Avatar asked Aug 17 '26 13:08

Roberto


1 Answers

Both recipes have problems.

Way 1 potentially allows to call a hook conditionally on component render, this may result in error ans is discouraged:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

As explained in this answer, this rule can be discarded, as long as it's guaranteed that a condition doesn't change between renders.

A way to guarantee this is to memoize a condition:

function useFunctionality(initState, enable) {
    const condition = useMemo(() => enable, []);

    if (!condition)
        return null;

    const [funct, updateFunct] = useState(initState);
    return funct;
}

Way 2 is more preferable because it's cleaner and useState call isn't expensive.

Both ways have the same problem, they use a state that never changes. If this is the intention, that's a use case for a ref. Since it never changes, a condition can be placed as initial value:

function useFunctionality(initState, enable) {
    return useRef(enable ? initState: null).current;
}
like image 137
Estus Flask Avatar answered Aug 20 '26 05:08

Estus Flask