For those pushing back, I wanted to share this talk given at a recent React Conf: https://www.youtube.com/watch?v=lGEMwh32soc
The idea (still just an internal prototype) is that one day your compiler might automatically memoize all React components and intermediate values for you. It obviously would be great to skip all the syntax noise, as well as the linter-enforced rules around correctly maintaining dependency arrays, etc.
But I think a big takeaway from this talk is that the React team sees memoizing as the norm. It is something to do by default, so much so that it may happen without your intervention one day. Immutability and value identity-based comparison are core to React. Violating these is the only way memoization can result in bugs (as some in this thread are worried about), and if you aren't following them then a lot of your React code is only working by accident to begin with, memo or no memo.
Following this articles advice will likely make your app render slower than not optimizing at all. Caching everything increases your risk of stale data through misconfiguration. Also the benefits of referential stability only apply to a limited set of components in your overall render tree.
Most data structures and components don't need stability. The issue people have with hooks is primarily caused by 99% of folks not quite understanding just how sparingly hooks should be used.
You should only worry about referential equality once you've profiled a performance issue from some high frequency interaction. This is the only time to useCallback, useMemo, or any form of referential caching.
An element walks into a bar. If your component accepts children or any react elements as props, quite normal, all optimizations around referential equality for its inputs will be flung out the window as that component de-optimizes at every optimization prematurely added.
Another big mistake I see folks of all levels make often is expecting more program design upfront vs. say just managing configurations.
- <Example><Test /></Example>
- <Example onTest={() => {}} />
- <Example test={{}} />
- <Example tests={[]} />
These all have the same render cost, so there is often no reason to extract function handlers/arrays/objects out of the jsx configuration.
I disagree with the author (and stated as much as recently as yesterday: https://news.ycombinator.com/item?id=32485460#32489682).
> With thousands of geometries in an interactive map, with lists and tables of items filtered by bounding box and categories, sorted by geodetic distance from wherever you were interested in, the performance issues that arose were never too many uses of useCallback or useMemo. The opposite caused issues multiple times however, of the app running haywire (or to a halt) kind.
The author here is taking their very valid and necessary use case for the optimization of memoization - non-trivial and expensive operations - and extrapolating from that to "just use useMemo, it's a best practice". Memoized functions are more complicated to reason about and therefore more bug prone than their alternative. Most client-side apps don't have thousands of records they're iterating over, they have dozens. Throwing around memo because one might one day become big, and you'd pay some performance penalty, doesn't seem prudent to me.
As a long, long time user of React (from back in the days before they even had a decent state management system and we were using libraries like "MartyJS" to handle state) I do feel like React has lost its way a little bit for regular users.
What benefits are the hooks providing over class based components to the average user?. I understand Facebook probably has very unique requirements around performance but 99% of people using the framework probably don't suffer from these problems but have to put up with the downsides of the occasional weirdness that hooks introduce.
Example, the way you have to handle useCallback is something that bit me very recently. I had a parent component tracking deleted gallery items as a map of ids and a boolean indicating if the image had been deleted. For some reason I couldn't figure out why the deletion map was always stale in the callback. Sure I had a useCallback around it but I'd correctly added the state variable in the dependency array of the useCallback hook.
Well, it turns out I need to pass the deletion map down to the child <Image /> components of the gallery where the deletion was called from because they also had a useCallback in them. The child image components have to know about all the images that have been deleted so that the parent component has the correct up to date state. Crazy!
I get that it's Facebooks project and they can do what they want with it, but I have 6+ years sunk into using the framework now, it's a lot to throw away. That said, I have high hopes for the combination of LiveView and Web Component based libraries like https://shoelace.style/
I remember having this discussion with less senior devs, & not convincing them at all of the value of building a stable practice. Having to think about whether we do things one way or another, having to decide, making the reasons for the decision known have huge costs. Re-evaluating as we make further changes has a significant chance of screw up.
Having reliable 99% straight shots that encompass a variety of cases is so preferrable. I've been trying to advocate for consistent, low-control-flow decision making for so long, & this feels like what should have been an easy win example for me to make the case on. Im glad to see the topic/example come up again, with a much better specific elaboration.