13

Consider this example:

import React, { useCallback } from 'react';

type UserInputProps = {
  onChange: (value: string) => void;
};

const UserInput = React.memo(({ onChange }: UserInputProps) => {
  // Is this `useCallback` redundant?
  const handleChange = useCallback(
    (event) => {
      onChange(event.target.value);
    },
    [onChange]
  );

  return <input type="text" onChange={handleChange} />;
});

export default UserInput;

My questions are:

  1. When the props consist of only onChange and no other elements, is the useCallback unnecessary in this case, since the entire component is already memo-ed based on onChange?
  2. If we add an additional prop (say a value for the initial value for the <input>), then I think useCallback becomes useful, since otherwise handleChange will be recreated even if onChange doesn't change but value is changed. Is that correct?

2 Answers 2

11

When the props contains only onChange and no other elements, is the useCallback unnecessary in this case, since the entire component is already memo-ed based on onChange?

It's memoized based on all props, not just onChange. Yes, useCallback is unnecessary there.

If we add an additional prop (say a value for the initial value for the <input>), then I think useCallback becomes useful, since otherwise handleChange will be recreated even if onChange doesn't change but value is changed. Is that correct?

If you want to update value and not update onChange when updating the input, you'd add value to your props and continue to use useCallback for your handleChange (if you like, so that React doesn't have to swap in a new event handler when it's setting the value; my impression is that's often overkill). E.g.:

const UserInput = React.memo(({ onChange, value }: UserInputProps) => {
  const handleChange = useCallback(
    (event) => {
      onChange(event.target.value);
    },
    [onChange]
  );

  return <input type="text" value={value} onChange={handleChange} />;
});

That will use the latest value and reuse the previous handleChange if the onChange prop hasn't changed.

You might not keep React.memo in that case, if you're expecting that value will get changed as a result of the onChange. That is, if you expect the common case to be that value will be different each time your component is called, then React.memo's check of the props is extra work you might not keep:

const UserInput = ({ onChange, value }: UserInputProps) => {
  const handleChange = useCallback(
    (event) => {
      onChange(event.target.value);
    },
    [onChange]
  );

  return <input type="text" value={value} onChange={handleChange} />;
};

But you can keep it if you want React.memo to keep checking the props and not call your function when neither prop changes.

If you just want to supply the initial value to your component and then control it locally, you could continue to use React.memo (since it still renders the same thing for the same input props), in which case you don't need useCallback:

const UserInput = React.memo(({ onChange, initialValue }: UserInputProps) => {
  const [value, setValue] = useState(initialValue);
  const handleChange = (event) => {
    setValue(event.target.value);
    onChange(event.target.value);
  };

  return <input type="text" value={value} onChange={handleChange} />;
});

That will only get called when onChange or initialValue changes. You could use also useCallback there in order to, again, only update the onChange on the input when the onChange prop changes, to avoid having React remove the old handler and set the new one when just the value changes:

const UserInput = React.memo(({ onChange, initialValue }: UserInputProps) => {
  const [value, setValue] = useState(initialValue);
  const handleChange = useCallback(
    (event) => {
      setValue(event.target.value);
      onChange(event.target.value);
    },
    [onChange]
 );

  return <input type="text" value={value} onChange={handleChange} />;
});

Side note: One thing to remember is that a new handleChange function is created every time your component function is called, even when you're using useCallback. It has to be, so it can be passed into useCallback as an argument. The only difference is whether you use that new function, or the original one that was created the first time (the result of useCallback). I think the reason for reusing the first one that was created for a given set of dependencies is to minimize changes passed to child components.

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

2 Comments

"you'd add value to your props, remove React.memo, and continue to use useCallback for your handleChange" -- why would I remove React.memo in this case? Wouldn't that cause UserInput to rerender even when no prop has changed?
@ZizhengTai - Good question. I've updated the answer to clarify that. You might remove it or not depending. If you think your component will be called a lot with the same value and onChange, you might keep it. But in that part of the answer I was assuming that onChange in the parent would update value nearly every time, which just makes React.memo's check of the props extra work. It totally depends, though, so again, good question. :-)
2

When the props consist of only onChange and no other elements, is the useCallback unnecessary in this case, since the entire component is already memo-ed based on onChange?

No, it might be necessary, memo and useCallback don't serve the same purpose.

Without useCallback you may have "heavy computation" on every render:

"heavy computation" refers to a generic case and not to this specific example where you only pass the event's value.

const UserInput = React.memo(({ onChange = () => {} }) => {
  // Uncomment for memoization
  // Note that you can implement useCallback with useMemo

  // const handleChange = useMemo(() => {
  //   console.log("heavy computation memoized");
  //   return event => {
  //     onChange(event.target.value);
  //   };
  // }, [onChange]);

  const handleChange = event => {
    // Here we can have some heavy computation
    // Not related to this specific usecase
    console.log("heavy computation on every render");
    onChange(event.target.value);
  };

  return <input type="text" onChange={handleChange} />;
});

If we add an additional prop (say a value for the initial value for the ), then I think useCallback becomes useful, since otherwise handleChange will be recreated even if onChange doesn't change but value is changed. Is that correct?

If you are going to use controlled component (due to use of value prop of input), the initialValue will be initialized once, so it pretty useless trying to memorize it (for what purpose?):

const UserInputWithValue = ({ onChange, initialValue }) => {
  // initilized once, no need to memoize
  const [value,setValue] = useState(initialValue);
  const handleChange = useCallback(
    (event) => {
      setValue(event.target.value)
      onChange(event.target.value);
    },
    [onChange]
  );

  // Controlled
  return <input type="text" value={value} onChange={handleChange} />;
};

Edit objective-brown-nyuyc

3 Comments

What '"heavy computation" are you talking about in the first part of your answer?
Its not related to this specific usecase where you only passing the event value, I tried to explain the generic case of "useCallback inside Memo"
I think I know what you're trying to say, it's just that the "Here we can have some heavy computation" comment above is in the change handler, not something creating the handler; it runs when the handler runs, not when it's built. Perhaps you're thinking of useMemo, where although you're creating a new function every time, you can avoid running that new function every time. (Not a factor for useCallback, because useCallback(fn, [deps]) is just useMemo(() => fn, [deps]);, that "build me a handler" function has no computation to speak of.) (Anyway, still a good answer.)

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.