196

Are there any benefits in using useMemo (e.g. for an intensive function call) instead of using a combination of useEffect and useState?

Here are two custom hooks that work exactly the same on first sight, besides useMemo's return value being null on the first render:

See on CodeSandbox

useEffect & useState

import { expensiveCalculation } from "foo";

function useCalculate(someNumber: number): number | null {
  const [result, setResult] = useState<number | null>(null);

  useEffect(() => {
    setResult(expensiveCalculation(someNumber));
  }, [someNumber]);

  return result;
}

useMemo

import { expensiveCalculation } from "foo";

function useCalculateWithMemo(someNumber: number): number {
    return useMemo(() => {
        return expensiveCalculation(someNumber);
    }, [someNumber]);
};

Both calculate the result each time their parameter someNumber changes, where is the memoization of useMemo kicking in?

Bennett Dams
  • 6,463
  • 5
  • 25
  • 45
  • 1
    The first will be `null` on the first render, while the second won't? – Jonas Wilms May 07 '19 at 19:08
  • 1
    *Are there any benefits in using useMemo (e. g. for an intensive function call)* - yes. You're using a hook that was designed specifically for this purpose. The example you listed is most common real world example for useMemo. – Estus Flask May 07 '19 at 19:42

3 Answers3

201

The useEffect and setState will cause extra renders on every change: the first render will "lag behind" with stale data and then it'll immediately queue up an additional render with the new data.


Suppose we have:

// Maybe I'm running this on a literal potato
function expensiveCalculation(x) { return x + 1; };

Lets suppose x is initially 0:

  • The useMemo version immediately renders 1.
  • The useEffect version renders null, then after the component renders the effect runs, changes the state, and queues up a new render with 1.

Then if we change x to 2:

  • The useMemo runs and 3 is rendered.
  • The useEffect version runs, and renders 1 again, then the effect triggers and the component reruns with the correct value of 3.

In terms of how often expensiveCalculation runs, the two have identical behavior, but the useEffect version is causing twice as much rendering which is bad for performance for other reasons.

Plus, the useMemo version is just cleaner and more readable, IMO. It doesn't introduce unnecessary mutable state and has fewer moving parts.

So you're better off just using useMemo here.

Danilo
  • 382
  • 7
  • 23
Retsam
  • 30,909
  • 11
  • 68
  • 90
  • I'm not 100% convinced if this rule would apply to every scenario. I'd like to know and try what would happen if it really was an expensive calc or if it was a network call where you might want to do two renders, one with a spinner and one with the final value. I guess that's what the new Suspense features are all about and perhaps they work fine with useMemo I did try a version, but it doesn't tell me much so I think it needs some rework https://codesandbox.io/s/usememo-vs-useeffect-usestate-ye6qm – Mark Adamson Sep 16 '19 at 15:15
  • @MarkAdamson It applies to every scenario in which you are using `useEffect` to synchronously compute a value and setting it with `useState`. It doesn't apply for asynchronous situations like where you'd want to show a loading spinner. – Retsam Sep 16 '19 at 19:28
  • 33
    I think the useEffect could be useful in some long running synchronous scenarios too. Check out the sandbox below. It takes 5 seconds to load because of the useMemo holding the render thread while the long calc runs, while the useEffect/useState one can show a 'spinner' while the calc is running so doesn't hold up the render: https://codesandbox.io/s/usememo-vs-useeffect-usestate-ye6qm @Retsam – Mark Adamson Sep 17 '19 at 07:06
  • 6
    besides optimization, I use `useMemo` instead of the `useState` + `useEffect` pattern because debugging is harder with more renders. – ecoe Sep 21 '19 at 12:45
  • 5
    It’s worth noting that the React API docs mention that `useMemo` doesn’t guarantee that the memoized function won’t be executed again if the dependencies don’t change because React may, in the future, discard cache to improve performance. So if the memoized function has side effects of some kind, it might be smarter to use a custom hook. – M Miller Jan 19 '20 at 19:20
  • Why will it render "1" again when the numberProp changes to 2 – Abhi Jun 15 '20 at 06:10
  • 3
    @Abhi Changing a prop triggers a re-render. But the value that is rendered is based on the `[result, setResult]` state, and `setResult` won't be called until the `useEffect` runs, which happens *after* the render. – Retsam Jun 15 '20 at 21:37
  • `useState` can recieve a function to calculate its initial value. So, if there is no need in recalculating memoized value based on props, then `useState` and `useMemo` are equal? `const [x] = useState(() => calcX())` is equivalent to `const x = useMemo(() => calcX(), [])`? – Dmitry Davydov Jul 04 '20 at 06:19
  • 1
    @DmitryDavydov I think that's true, but if the `calcX()` calculation doesn't depend on any state or props of your component, you can pull it out the component as a constant and don't need to use a hook at all. – Retsam Jul 05 '20 at 15:38
  • 8
    Can we conclude from this that `useEffect` + `useState` is the right solution in situations where the thing to compute is `async`, because the value cannot be available in the current render anyway? Related [question](https://stackoverflow.com/q/61751728/1804173). – bluenote10 Nov 08 '20 at 22:52
  • @bluenote10 Yeah, it doesn't generally make sense to memoize the promise itself, so you'd instead want to update state based on the result of the promise. – Retsam Nov 11 '20 at 18:25
  • I see as wrote from other above that useMemo suite way better case when there are exteremely expensive calculation that do not depend on states or any side effects, so more for pure "funcions" case – Carmine Tambascia Feb 16 '22 at 18:12
  • 1
    @MarkAdamson quite the opposite, is better use useEffect + useState when there is a need of an async code that it's by nature a side effects, meaning his happening outside and you don't know exactly when, as here others have already pointed out https://stackoverflow.com/questions/61751728/asynchronous-calls-with-react-usememo – Carmine Tambascia Feb 16 '22 at 21:41
  • Thanks @CarmineTambascia, it's been a while since I posted that so don't recall the context. But I don't think our statements are contradictory. I was identifying a time when useEffect could be useful for synchronous actions that don't have side effects vs. usememo. It's still the case that useEffect is intended and designed for helping with side effects as you say. – Mark Adamson Feb 17 '22 at 23:43
  • 2
    "In terms of how often expensiveCalculation runs, the two have identical behavior" - not necessarily. See [this answer](https://stackoverflow.com/a/67131354/5134722). "useEffect guarantees that it will not be fired if dependencies have not changed. useMemo does not give such guarantees." So there is a semantic difference. useMemo is allowed to run the expensive calc more than once even if the dependencies didn't change. – Colm Bhandal Sep 02 '22 at 15:41
26

I think there are two main points you should consider when choosing between them.

  1. Time when function called.

useEffect called after component has been rendered, so you can access DOM from it. For example, this is important if you want to access DOM elements via refs.

  1. Semantic guarantees.

useEffect guarantees that it will not be fired if dependencies have not changed. useMemo does not give such guarantees.

As stated in the React documentation, you should consider useMemo as pure optimization technique. Your program should continue to work correctly even if you replace useMemo with regular function call.

useEffect + useState can be used to control updates. Even to break-up circular dependencies and prevent infinite update loops.

schernichkin
  • 1,013
  • 7
  • 15
  • 1
    useEffect does NOT warranty that it will not re-render when dependencies have not changed. useMemo is the one that does not fire when dependencies have not changed. I have never had issue of extra re-renders on useMemo, contrary to useEffect that sometimes re-renders when dependencies have not changed. Instead, you want to watch out for the fact that useMemo struggles to detect some changes in some dependencies that are complex in structure. (for example, a change in an object that is inside of an array). – GiselleMtnezS Dec 16 '22 at 16:58
18

I would say other than the async nature, there might be some difference in terms how they are designed.

useEffect is a collective call, async or not, it's collected after all components are rendered.

useMemo is a local call, which has only something to do with this component. You could just think of useMemo as another assignment statement with benefits to use the assignment from last update.

This means, useMemo is more urgent, and then useLayoutEffect and the last being useEffect.

windmaomao
  • 7,120
  • 2
  • 32
  • 36