'useCallback with onchange function in React JS

I have 2 components. In the parent component I have this:

const Demo = () => {
  const [state, setState] = useState(true);
  const onChange = useCallback(
    (value: string) => {
      console.log(value);
    },
    [],
  );

  return (
    <div className="a">
      <button onClick={() => setState(!state)}>sds</button>
      <div className="123">
        <Bar searchHandler={onChangeSearchHandler} />
      </div>
    </div>
  );
};

In the Bar component I have this:

const Bar = ({ searchHandler }) => {
    console.log('bar');
  return (
    <div>
      <input type="text" onChange={(value) => searchHandler(value.target.value)} />
    </div>
  );
};

Wrapping onChange with useCallback I expect to cache the function and when I click on <button onClick={() => setState(false)}>sds</button> I don't want to render Bar component, but it is triggered. Why Bar component is triggered and how to prevent this with useCallback?



Solution 1:[1]

This has nothing to do with the onChange function you're wrapping with useCallback. Bar gets re-rendered because you're changing the state through setState in its parent component. When you change the state in a component all its child components get re-rendered.

You can verify it yourself by trying this:

const Demo = () => {
  const [state, setState] = useState(true);

  return (
    <div className="a">
      <button onClick={() => setState(!state)}>sds</button>
      <div className="123">
        <Bar />
      </div>
    </div>
  );
};
const Bar = ({ searchHandler }) => {
  console.log('bar');

  return (
    <div></div>
  );
};

You'll see that the Bar gets re-rerender anyway.

If you want to skip re-rerendring any of the child components, you should memoize them using React.memo when applicable.

Also, you should familiarize yourself with how state in react works and how does it affect the nested components as this is a main concept.

Solution 2:[2]

The issue is that you haven't used React.memo on Bar component. The function useCallback works only if you use HOC from memo.

try this, in Bar component create this wrapped component:

const WrappedBar = React.memo(Bar);

and in parent component use this wrapped bar:

const Demo = () => {
  const [state, setState] = useState(true);
  const onChange = useCallback(
    (value: string) => {
      console.log(value);
    },
    [],
  );

  return (
    <div className="a">
      <button onClick={() => setState(!state)}>sds</button>
      <div className="123">
        <WrappedBar searchHandler={onChangeSearchHandler} />
      </div>
    </div>
  );
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2