Home > Mobile >  React: to useCallback or define in inner component?
React: to useCallback or define in inner component?

Time:05-12

I have two components, such that they are:

const ChildOn = (/*...*/) => {
  //...
}

const Parent = () => {
  const [is42, setIs42] = useState(true)

  return (is42 ? <ChildOff ... > : <ChildOn ... />)
}

The definition of ChildOff is no important.

I want to define them as either of the following, yet I can't decide which:

  1. Declares functions used in children, based on a variable/function in the parent, inside each child.

    type ChildOnProp = { setIs42: Dispatch<SetStateAction<boolean>> };
    const ChildOn = ({ setIs42 }: ChildOnProp) => {
      const f1 = () => { setIs42(true); };
      return <Text onPress={f1} />;
    };
    
    const Parent = () => {
      return is42 
        ? <ChildOff setIs42={setIs42} /> 
        : <ChildOn setIs42={setIs42} />;
    };
    
  2. Defines functions used by children, inside the parent.

    type ChildOnProps = { func: () => void }
    const ChildOn = ({ func }: ChildOnProps) => {
      return <Text onPress={func} />
    }
    
    const Parent = () => {
      const [is42, setIs42] = useState(true)
      const f1 = useCallback(() => { setIs42(true) })
      const f2 = useCallback(() => { setIs42(false) })
      return (is42 ? <ChildOff func={f2} /> : <ChildOn func={f1} />)
    }
    

While (1) is much prettier to me, (2) seems a lot more efficient. Yet I don't know if I'm apt to judge that, since I've read many articles on React contradicting each other on when it's best to use useCallback.

CodePudding user response:

The React community warns against useless memoizing as it could add complexity without any of the benefits in some situations.

In this case, I think it's worth memoizing the callbacks since it will reduce the number of unnecessary renders of the child components, which otherwise could have negative impact, or even introduce issues down over time.

To do this, there's a common pattern of creating a custom hook, often called useToggle, which memoize common setters.

import {useState, useMemo} from 'react';

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  // Defined once, so guaranteed stability
  const setters = useMemo(() => ({
    toggle: () => setValue(v => !v),
    setFalse: () => setValue(false),
    setTrue: () => setValue(true),
    setValue,
  }), [setValue]);

  // Defined each time the value changes, so less than every render.
  return useMemo(() => ({
    ...setters,
    value
  }), [value, setters]);
}

This can be used in the parent as such:

const Parent = () => {
  const { value: is42, setTrue, setFalse } = useToggle(true);
  return (is42 ? <ChildOff func={setFalse}> : <ChildOn func={setTrue} />);
}

TypeScript will infer all the types automatically, so it works out of the box.


If there's multiple state values that need a toggle callback, we can easily identify each one by not destructuring.

const firstState = useToggle(true);
const secondState = useToggle(true);
//...
return (
  <>
    <Foo onClick={firstState.toggle} />
    <Bar onClick={secondState.toggle} />
  </>
);

As a reference, here's a simpler implementation from Shopify. As for memoizing too much or too little, Meta (Facebook) is apparently experimenting with memoizing everything by default at transpile time, so that devs wouldn't have to think about it.

  • Related