Hacker News new | ask | show | jobs
by tbezman 1699 days ago
Potentially controversial post on why we memo all the things. https://attardi.org/why-we-memo-all-the-things/
6 comments

I also generally fall on the side of useMemo'ing everything pre-emptively, and this is a good argument for its effects on performance. However I think a more important effect is that preserving references between renders (and lack thereof) now has a direct effect on behavior due to useEffect's dependency list.

If a downstream component's useEffect depends on a prop that hasn't been useMemo'd, their effect will be executed on every render due to the changing reference, and there is nothing that can be done at the downstream component to alter that behavior. This means they'd have to trace the prop back to its source and refactor it to useMemo, which can be a very painful exercise as I'm sure anyone who has done it can attest to. For props that come from third party libraries, this might not even be an option, which is why pre-emptively useMemo'ing is even more crucial for reusable abstractions like shared hooks and components.

And yes, I realize that the React docs currently recommend using useMemo only as a performance optimization, not as a semantic guarantee, but I believe that ship has sailed. There is so much code in the wild today that useMemo and then pass the result to some useEffect downstream, that they can't really afford to break in practice. I think the only practical option at this point is to strengthen the original useMemo with a semantic guarantee, and then introduce a separate useMemoForPerformanceOnly (with a better name) hook that can be used to opt-out of the semantic guarantee to allow React to evict memoized results to optimize for memory usage.

I feel like you could go more into depth to defend your assertion "no memo is not an option." As someone who has spent hours on jsperf back in the day -- my apps feel blazing fast already. Many devs I know also don't use memo or useCallback at all. It sounds like you're adding an entire layer of extra abstraction "just in case." But then again, maybe your use case makes it warranted.
Same, I haven't seen a need for it for performance yet across a few companies and open source apps I've worked on.
Every single time I’ve needed to use memo the performance gains where obvious.
A: You'll know when you need it. Until that point one is firmly in the realm of premature optimization.
The post points out that given memoization in many cases avoids serious perf/functional issues not memoizing may be considered premature optimization.
Interesting post, makes u wonder why the hell doesn't react do it by default. I'm really surprised to see that i have to think about performance in react apps so much, coming from elm i expected it to be all faster by default
Because Elm has language-level support, while hooks try to do things that should be language-level but aren't, which is why they have so many footguns in general.

To get concrete, there's no possible way this function could be automatically memoized at a library/framework level:

  const myCallback = () => setFoo("bar")
I wonder every day why React doesn't do a lot of things by default. Imagine specifying memoization dependencies by hand. Most of the stuff is named or put in the framework to raise engineering salaries for making CRUD applications or to make them feel smarter about doing basic web work.

React will go the way of JQuery in 5 years. We'll kindly thank it for it's contribution to the framework space and move on. No one in their right mind would pick it up for new apps over Vue, Svelte, Angular hell even Ember. Then you have the new wave of frameworks coming out. Only way that React is usable is only for the view layer with Mobx holding any other state.

Makes sense. How about making that the default behavior of React and providing a React.dontMemo(…) API to opt out?
Or even better, provide a top level configuration option, like:

ReactDOM.render(element, document.getElementById('root'), {memo: true});

Just dreaming...

This one is interesting, because React has a separation between the renderer and the library. There is a different renderer for e.g. react native, or even for the terminal (!!). It’s a little ambiguous where memoization falls (does it belong to the renderer, or to the core?). I totally agree that being able to switch on behavior for an entire tree or subtree would be ideal, but react tends to use components for that as well (e.g. <React.StrictMode>). Perhaps this should be a component, it could even be implemented via the context system.
This is great. And great explanation of the reasonings.

I wonder why React doesn't provide a configuration/flag/option to just do this by default if anybody wants to.

The problem with using useMemo & useCallback everywhere is that the dependency arrays are really easy to get wrong. Small changes can easily lead to stale data or render loops. I'd much rather people take the time to consider each case. The React profiler is your friend here.
I think if you use them 100% of the time alongside eslint dependency arrays are really hard to get wrong. If you rarely use them, you always get them wrong.
If you just blindly follow the eslint suggestions you can very easily wind up with render loops. You need to carefully consider and understand the dependencies in each case. And if you want to do something specific when only one of your dependencies changes you need even more gymnastics.
The reason you end up with render loops is usually because you didn’t memo something. If you memo everything you can avoid that issue.