Hacker News new | past | comments | ask | show | jobs | submit login
Understanding UseMemo and UseCallback (joshwcomeau.com)
120 points by feross on Aug 30, 2022 | hide | past | favorite | 84 comments



While I personally like React, I have to say the design of the library is just so awful and non-intuitive. While I can maintain a legible codebase myself, as soon as juniors in my work are involved I have to spend a lot of time reviewing PR's, fixing changes and explaining concepts in order to make sure that the application actually runs correctly and isn't, say, infinite looping in a useEffect or something like that.

I only rant about this here because I think useCallback is pretty awful design - separate functions that only exist as syntactic sugar are terrible for user readability. A lot of people probably know useMemo as it's quite easy to understand and used somewhat frequently, but if I were to use useCallback in my code it would probably cause a lot of others at my work to then have to go scouring the docs to figure what the heck this hook exactly does. On the other hand, just forcing users call useMemo and return a function means that everyone who knows useMemo knows what's happening.

Unless I'm completely misunderstanding how useCallback works, perhaps React actually does some extra optimization under the hood or for this or something.


> I have to say the design of the library is just so awful and non-intuitive.

Absolutely agree. But this wasn't always the case. The original class components were much easier to understand. A backend developer could open a .tsx file and understand what is going on, and make simple fixes. This was one of the strengths of React - it was so clean, compared to Angular and other frameworks.

But then they introduced hooks and messed it up. Now it is not intuitive in the least. Hooks makes some things easier but the cost in terms of loss-of-intuitiveness is huge. The other mistake Facebook is making is assuming that performance is the most important attribute of a UI framework. It is not. For 99.9% of developers the original React was already fast enough. They should have focused on making the mental model cleaner, and smoothing some of the roughness of the lib such as updating the props of a stateful component, handling deeply nested objects in the state without making entire copies of the data and so on.


I don’t think it was done for performance, hooks just compose much better than class components. Definitely a trade off in terms of blank-slate intuition, but it unlocked a lot of things in the realm of reusability.


What does "compose much better" mean?


copy/paste from another comment:

Hooks compose, whereas side effects and memoized values sprinkled through component constructors and lifecycle methods do not.

For example, the equivalent of useEffect required calls inside of componentWillMount, componentDidUpdate, and componentWillUnmount. You try and make something like this re-usable and you’ll be leaking details of your implementation across the whole component via inclusion in these lifecycle methods, not to mention any data you’re shoving onto the component instance. But it’s still doable.

Now, what if you wanted to use this re-usable behavior inside of another re-usable behavior? It gets complicated fast! Now your library needs to expose the lifecycle-updating methods of the underlying library, leaking details all the way down. Hooks are opaque from the perspective of lifecycle, while still having access to all the same… hooks.


Oh ok. I'd agree Hooks compose much better then. But there was no attempt or consideration at compositional API for class based components besides getting rid of it entirely and then adding functions that hook into the nether nether.

> For example, the equivalent of useEffect required calls inside of componentWillMount, componentDidUpdate, and componentWillUnmount.

Yeah from what I recall this was the prime motivational example given in the React docs for introducing hooks. It's a legit problem. But hooks and getting rid of class based components is not the only solution. In classical OO are also well worn solutions along the lines of hooks, they just work with classes / objects with interfaces still. It's somewhat frustrating that this is never even considered.


It depends, they compose better until they don't. It is easy to mess hooks up if you do not call them in the right order, especially if they are nested (composed) in other functions. You need discipline to use them correctly.

Ironically they could have prevented this by making them more functional. Instead of some magic hidden state, you should have to always pass a context to them, and you should have to give every hook a unique name or a Symbol().


I know they considered “keyed” hooks, but that api is a lot more verbose since you need to key _every_ hook you use, and hooks internally need to create a sub-hook context / namespace to avoid collisions which either needs to be managed manually _or_ means you can’t use plain functions as hooks (there would be some createHook function you’d run instead).


> The original class components were much easier to understand.

Class components only made you feel like they were easier to understand because the method names indicated an intuitive lifecycle. But the reality under the hood was almost certainly different than your mental model because the method/event handler names were actually deceptive. Hooks are much closer to the metal, so to speak.

> A backend developer could open a .tsx file and understand what is going on, and make simple fixes.

They might assume they knew what was going on and how their "simple fixes" would affect execution, but that would probably be naïve. But that goes for anyone jumping into any codebase for a platform/library that they're not familiar with.

> Hooks makes some things easier but the cost in terms of loss-of-intuitiveness is huge.

This is fair, although I feel like the learning curve for React is only steep at first, then plateaus for the vast majority of application developers. It only gets really complicated (as anything does) when you start to get into advanced performance tuning or you're writing a library with very specific requirements.

> The other mistake Facebook is making is assuming that performance is the most important attribute of a UI framework.

This is simply not true. The React team has indicated many times over the years that basic, hello-world level, idiomatic React code is fast enough for the vast majority of applications ("don't worry too much about the number of re-renders," etc.). They're very anti-premature optimization as a group.

Also, Hooks weren't introduced to improve performance anyway (at least that wasn't the primary motivation).

> handling deeply nested objects in the state without making entire copies of the data

It's not from the React team, nor is it specific to React, but have you tried immer? https://immerjs.github.io/immer/


See this is interesting because I always found it hard to keep track of the lifecycle and state changes in React classes (especially in actual production code which quickly devolved into a tangled mess) but found functions and hooks extremely intuitive and once they were introduced. And refactoring and composability became way easier.


While I agree with with that useEffect can be non-intuitive and easy to misuse, I think useCallback is pretty nice to have.

From what I'm aware, it's as you said. It's simply a syntactic sugar for useMemo if you want to memoize functions.

Now if useCallback doesn't exist, you have to use useMemo and pass in a function to the function. Something like `useMemo(() => () => ...)`. Sure if you know what useMemo does then you'll know what it does. But I have a hard time believing that juniors would find that easier to read than useCallback(() => ...).

All I need to say to them is: "Hey, if you want to memoize a function, use useCallback instead". And if I see useCallback used anywhere, I know for sure that it's memoizing a function. Rather than saying: "So you see that there's an extra bracket there? That means that you're returning a function. Yada yada"

There's a grand total of 15 hooks[1], most of which you probably never ever have to use. I would expect all React developer to go through them all to see what they're about.

I would also expect them to know & remember most of the basic ones: useState, useEffect, useContext, useReducer, useCallback, useMemo and useRef. That's a total of 7 functions. If they can't do that then I question the quality of today's developers.

[1] https://reactjs.org/docs/hooks-reference.html


> I would also expect them to know & remember most of the basic ones: useState, useEffect, useContext, useReducer, useCallback, useMemo and useRef. That's a total of 7 functions. If they can't do that then I question the quality of today's developers.

Exactly. I’d rather write the code myself than have to review the code of someone who can’t understand such a basic set of concepts.


But see, you're not thinking enterprise.


"useCallback" is not very intuitive naming. I'd guess a fair bit of the confusion could be attributed to this alone. Could have been useMemoizedValue useMemoizedCallback at least. The whole useXXX naming convention is also unintuitive.


Or the term "side effect" (especially in a language that does not enforce return types): https://dev.to/ruizb/side-effects-21fc


I’d argue that useReducer and maybe even useRef most people could get away without knowing.


useRef is pretty important. useReducer though could be removed and I wouldn't notice.


I'm inclined to agree with you since I don't use useReducer as well. But there are projects that are littered with that -w-


> as soon as juniors in my work are involved I have to spend a lot of time reviewing PR's, fixing changes and explaining concepts in order to make sure that the application actually runs correctly

I've never worked on a codebase where this isn't true regardless of what frameworks or libraries are used - that's kinda part of the gig.


Exactly. I don't know why people pick on only React for this. Any un-opinionated library or toll will have the same problem.


IMHO (only), React is a bit too flexible/modular/unopinionated and asks you to reinvent a lot of wheels... everything from basic page routing to syncing ajax calls to loaders and states, etc. More opinionated or bolts-included frameworks have defined conventions that you're strongly encouraged to adhere to, and they work great for most use cases. And when you see that pattern again, you know what the intent is.

But with React, every dev ends up doing the same thing their own way, with subtle differences and subtle bugs, and then as turnover happens and frameworks become less fashionable, codebases get polluted by a mix of different dev and tool generations and it gets even harder to ensure correctness.

Using a more opinionated framework built on top of React, like Next.js, can solve a lot of those issues... until Next itself becomes unfashionable and the cycle starts again :(


Yeah React is not meant to give you this layer. NextJS indeed does. Why do you care if it becomes unfashionable in the future? Pick one tool that fits your use case and run with it for its lifetime. If you need to learn something else 5 years from now, learn something else. I’ve been in software for two decades almost, I’ve always had to learn new frameworks and libraries, part of the job isn’t it?


It's not that I mind learning new things, it's that React is so barebones everyone tries to make it complete by adding different things. One person likes redux, another mobx, another context... some like class components and others like functions, some use libraries and other use frameworks, etc. Sometimes all in one codebase if it's old enough. It's a lot harder to maintain over time than a more opinionated framework. I wish Angular won out, myself.


It's all just javascript at the end of the day. What you're describing is exactly why many people like React.

> It's a lot harder to maintain over time than a more opinionated framework.

That's not true at all, that depends on the quality of the codebase the exact same as if it were built on an opinionated framework. I've seen plenty of bloated Rails codebases that were miserable to maintain.


React works great for simple examples and small applications. When you try to build something complex with good performance then the difficulty goes almost vertical. It’s trivially easy to miss or break a stable reference somewhere and tank performance or show stale data.


What other FE Framework does a better job at scaling up in your opinion?

I work as a freelance FE dev for 8 years and been doing webdev since the late 90s. I must say that for SPAs, React (with hooks) does the best job at scaling up (if you pay attention to your architecture) and I have seen and used frameworks/libs come-and-go such as knockoutJS, jquery, angular1, angular2+, (p)react etc.


I haven't tried any of the other comparable frameworks at similar scale. Both Svelte and SolidJS seem much nicer than React to me on the smaller projects I've tried them on but I haven't used them on large enough projects to make a direct comparison.


>React works great for simple examples and small applications.

I'd like to have some citation here. Most of the product startups use React for their frontend, and they seem to make it work really well. Never saw any issues.


I'm citing my own experience working on several large high performance react apps. Yes you can make it work. No it's not easy at all.


I converted a long list from Django templates into react. I struggled to convert the code to navigate to a hidden element.

In JavaScript it's document.getElementById and set attribute. In react it's easy to cause part or the entire list to recalculate the virtual dom.

I couldn't think of a good solution. Ideally I'd just call set state on the child element that needs updating but that would require registering all the relevant functions with the parent. Super ugly.


Can you explain? Are you saying there is a long list, with some hidden elements that need updating (but not rendering)?

If so... can the entire list be stored in state (and updated as necessary), but only the ones marked "is visible" actually get rendered?

Maybe I'm misunderstanding completely...


So in Django templates I'd just flip a style - display from hidden to block of a single div. This would reveal a collapsed section of the tree and I could scroll to the correct div id. (saying list was a bad simplification)

In react because the state flowed from top to bottom I have to be very careful about what objects I update. Just passing down a single reveal could force the virtual dom to update for (num children per node)*depth. Even this required care on what props got updated.

The solutions seemed to be: 1. lots of useMemo so only a minimal amount of a node got recalculated; 2. Directly call a set state function of another child.

Both are quite ugly :( but the second seems nicer as it never could force a full update. However, it seemed to require a registry of set state functions {id: setState}.


Isn't useCallback to solve the common mistake of:

<button onClick={() => setText("clicked")}>click</button>

Without useCallback this would update the dom on every render.


Unfortunately the "update on every render" is deeply anchored into devs minds, although in most cases play a minor role. It comes back from a time where browser JS engines where really slow to create lambdas - which is not the case anymore. Additionally, react doesn't really update the DOM on "render" (the internal react render cycle) - due to DOM diffing.

My best advice is (also apart from React): Do not spend time on imagined/anticipated performance bottlenecks; only ever spend time on performance tuning if there are real (perceived, not measurable!) performance issues (there has to be a profiling report in your hand).


Yeah I only started caring about this when tracking down lag in my app. Though it's fun to think about :)

I believe this causes a real Dom update because it can't know the onclick function hasn't materially changed. It's just diffing function references.


The main use for useCallback is to add a callback to a useEffect dependency array without it running on every render.


That's not a mistake lol.


Can you explain why it isn't? I'm probably misunderstanding something.

setText doesn't change because it's from useState. The only reason to update the Dom is because the onclick function isn't memoized.

Updating the Dom every render isn't terrible but it isn't good.


button is just a small lightweight element usually with a single string child. If passing a lightweight unwrapped click handler to a leaf node is a faux pas then honestly the react team needs to memoize by default. Your description "updating the dom every render..." And acting like indiscriminate useCallback is a panacea for your performance problems to me seems like a pretty naive take. But then again seems like the community opinion in this has shifted, I just honestly don't get it. And I do use useCallback btw, but only when I need to keep referential stability for known reasons,not as an automatic default


Just use imba and get much better performance and dx without the pain.


His previous one on rerenders was fantastic... many lightbulb moments.

https://www.joshwcomeau.com/react/why-react-re-renders/

FB should hire him to rewrite their docs.


This is really excellent. I have a hook that is producing updated data every second, it’s pretty annoying that this causes everything below where the hook was included to be re-rendered each time, even if the updated data isn’t included. I can’t help but think using redux and connecting components to a global store as far down the hierarchy as possible was much more efficient. Is there an alternative way to do this with hooks to avoid re-rendering?

One thing I’ve started doing everywhere is putting a logger.debug(“rendering WidgetX”) at the top of every component to catch anything weird as I go along, it’s been pretty helpful.


You can use React.useMemo to prevent children from rerendering when passed the same props. I'm not 100% sure if that's what you are describing.


from the article: "So, to go back to our misconception: props have nothing to do with re-renders."


Oops meant React.memo not useMemo

https://reactjs.org/docs/react-api.html#reactmemo


And nevertheless React.Memo does exactly what they said - it prevents a component from rerendering unless it’s props change. Likewise useMemo can be used for a similar function - to prevent a component from rerendering unless the dependencies to the useMemo call change.


Push the hook down to the lower components, and use it like you would redux?


But I need things the hook does elsewhere, as I say I’m not even using the stuff that updates in the main component…

Maybe I can separate the hooks into sets the data and reads the data somehow…

Might just return to redux to be honest for this bit at least…


Yeah separating out sets and reads is one of the reasons Redux exists.


React's push towards a functional model with all the use* hooks is so sad to watch.

A stateless programming model in a domain where state is so fiercely coupled to program utility is inevitably going to spawn this garbage.

Class based components with MobX managing state was, and still is, a dream to write __and__ read.


I wish people would stop saying this. Hooks aren’t functional or stateless.

Literally, calling ‘useMemo’ twice returns two different objects, because the function keeps track in internal state of how many times it has been called.

Hooks are very deeply imperative.

They are a way of obtaining some benefits that are easily obtained in functional programming via higher-order functions, but expressed in a way that makes them easily consumed in an imperative code body.


Hooks are obviously not stateless - I'm saying they were an inevitable consequence of the push towards functional components and that they represent an inferior development experience. No one needed to write 2000 words on how to use setState.


Couldn't disagree more. Do you remember all the nonsense you had to read about componentWillUpdate, componentDidUpdate, getDerivedStateFromProps, componentWillMount, componentWillUnmount? What a mess. Hooks clean up so much of that complexity.


Maybe this has more to do with how people look at these paradigms. I find the class based lifecycle methods much easier to wrap my head around than hooks. I think hooks are more of an answer to higher order components. They make sense to me when I look at them that way, but as a replacement for classes, function components seem inferior from a maintainability perspective.


That is all sorts of funny for people who remember PureComponent and all the hype about functional components.

React has always been an exercise in smoke and mirrors.


Function components with MobX Observers are a godsend and far better than anything Redux. MobX doesn't get enough credit imo.


Class based components are garbage. There's a reason hooks were introduced. It is analogous to OOP vs functional programming, inheritance vs composition (not exactly the same because hooks are not pure functions).


Yeah not quite the same but very analogous because hooks are designed to compose much as pure functions do.


I love Josh's writeups. His CSS for JS devs course is also fantastic for folks who want to get better at CSS.


I always enjoy Comeau's blog posts, they're the essence of interactive learning. For real world things, I also like Bartosz Ciechanowski's work [0], they have very highly detailed renditions of things like mechanical watches and internal combustion engines.

[0] https://ciechanow.ski/


Don't forget there are other paradigms out there!

https://www.solidjs.com/guides/reactivity


Once you add useMemo and useCallback, your code will become pretty unsightly though. I wish React had a better solution to this problem.


You can largely avoid useMemo by keeping your props and state in the format you actually need them.

The solution for more complex useMemo and useCallback usage is to create your own hooks.

You can compose all the hooks into a single hook that your component uses and return from that hook whatever your component needs. This is common with components that have a lot of event listeners that can trigger local state and/or redux state changes.

We have a spreadsheet component and the individual cells had some pretty gnarly hooks and after pulling them into their own custom hook we've been able to maintain them nicely.


This talk suggests they're exploring ways to improve the DX: https://youtu.be/lGEMwh32soc


Agreed, many libraries and frameworks have built in support and simpler syntax for computed values, like MobX, or even older frameworks Ractive or Knockout. React takes a very "code-y" approach to this, which I guess some people like.


I’m in the process of converting a large knockout app to react and computed values are definitely what I miss most. They’re defined on the class (normal react practices prefer everything to be a plain object), they can reference other values/computeds internally just by calling this.computed (in react you have to manually chain the results through useMemo calls, and even with reselect there’s the added step of specifying all of your dependencies), they’re computed on demand and track their dependencies lazily (in react if you want a conditional useMemo you have to just early return null from the callback), and they’re conveniently useable in both model code and component code. I haven’t found a way to convert these that doesn’t feel like a complete regression in terms of code readability and ease of use.


Honestly, you should check out MobX, it's really great for former Knockout devs/apps.


Thanks for the suggestion!


I love the way this is put together. Just outstanding work - totally readable with interactive illustrations. Hats off!


I completely understand why you would want to memoize values and reasons to use useMemo. But I don't really understand why you would want to memoize a function to avoid it being re-generated. I guess maybe it saves a tiny performance hit, but does useCallback result in significant performance improvements?


If you're passing the callback as a prop to a child component, then it will cause that component to be re-rendered every time the parent component renders unless you memoize the callback. This happens because React checks referential identity when determining whether props have changed. The performance hit of unnecessary re-renders can be fairly significant in large React apps.


React will rerender the child component anyway in this case even with useCallback. Of course you can prevent this by using React.Memo on the child component, and in this case using useCallback makes it possible to optimize rerendering by providing stable values for functions you pass to that component.

Adding useCallback for functions only does something useful if you also React.Memo those children. So while this is a very important optimization, it's not one you need to apply to everything.


Memoizing the callback prop only matters if you wrap the child component in "React.memo".

If you don't wrap the child component with "React.memo", every time the parent renders the child will render regardless of prop equality, even with memoized props/callbacks.


It actually doesn't directly help performance at all, because you're still creating a new function each time; they just get thrown away after the first time (until dependencies update)

It only exists/is done to give you a stable reference so you can avoid triggering downstream dependencies (hook arrays and React.memo'd components)


Does a function really exist if it is never called?


Yes, it gets allocated in memory (assuming there isn't some kind of crazy runtime optimization that can figure out it won't be called and skip doing that)

One reason useCallback exists is because useMemo, too, allocates a function on every render- even if it doesn't recompute the underlying value by actually calling the function. So if you're going to be allocating a function anyway, there's no point (performance-wise) to allocating a function that creates a function. You can just cut out the middle-man


It's an exponential speedup.

The virtual DOM is a tree, and if roughly balanced, the path that needs to be updated is of size log(N) compared to the full tree of size N that will be updated by default.

In practice, it's the difference between a stuttering app that lags at every keypress in an input field, and something usable.


Is this not extremely useful for hook interfaces?


Why is Josh creating a useCallback with an empty array? Doesn't that result in a cache that gets invalidated every render (ie does nothing)?


No, an empty array means that the cache is never invalidated.


Really nice, but works badly on mobile with a small screen.


So useMemo === Vue's computed values??


Nothing wrong with this blog but I'm pretty sure the exact title has been done 1000x


I Googled "Understanding UseMemo and UseCallback" and there were 7 results, all referencing this exact article.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: