I know that the conventional wisdom (e.g. https://kentcdodds.com/blog/usememo-and-usecallback) is not to do that, but it's worth noting that Kent doesn't actually bring receipts for his claims, and also the discussion is typically limited to toy examples, not huge applications that have the potential for huge cascading renders when anything changes. In my own profiling of real world applications I have found that memoizing everything is not actually a performance regression. And then also consider the React Compiler, which of course does the same, at an even more fine-grained level than any human could be bothered to consistently do by hand.
I think we lack the primitives to understand and discuss messy complicated real world frontends. Something about UI state and its coupling to servers, plus time, plus Conway's Law, causes a real messy situation that appears to be much easier to avoid on the backend than on the frontend.
This creates a massive gulf between beautiful simple frontend toy examples and the reality of applications made with those same frameworks when deployed for multiple years and product cycles. I think it is a big part of what fuels shiny framework adoption. You think: "yes, this here is what will resolve the complexity and pains I see in my work codebase", and this is bolstered by your carefully curated personal side projects.
I'm not in frontend mainly, but this article, and another short I watched about how useEffect is almost always wrong makes me feel uneasy about React. It's insidiously complex.
A bunch of `useCallbacks` are rendered ineffective because the top of the chain wasn't properly computed. And the fix isn't `useCallback`, its to rearchitect the tree. This feels wrong - either the API, the framework, or the language isn't powerful enough to ensure that someone does the right and performant thing. It seems like the new React Compiler tries to fix this - but I'm surprised React Compiler is a thing that needs to be built.
> This feels wrong - either the API, the framework, or the language isn't powerful enough to ensure that someone does the right and performant thing.
It's the language. React uses a bunch of magic methods and requires you to follow rules about when to call them, with limited guardrails, because if you try to make developers do this properly (with higher-kinded types and monads) they throw their toys out of the pram. Our culture prefers "there are a bunch of subtle rules you have to follow and if you break them you'll get weird bugs or performance issues" to "there are a bunch of explicit rules you have to follow and if you break them your code won't compile", sadly.
Svelte is a lot closer to that latter style, there are a lot of React weirdnesses that just aren’t possible in Svelte (because it’s a compiler, not a framework)
I'd rather break my hands than use Svelte instead of React again. It comes with so many problems of itself. Summarized in a single sentence though it would be: It's more common than you'd like it to be, that your UI doesn't actually reflect the state of your application.
I was considering investing in learning Svelte to avoid some of the React quirkiness, but this is not very promising. Can you share a bit more? Trading off a set of quirky annoying things for an equivalent or worse one would be suboptimal.
Not typescript, but elm essentially promises that. Unfortunately it's been pretty much abandoned while barely scratching the surface of javascripts api (and had drama when the community attempted to make their own native code)
In my experience it just abstracts all the same issues behind its own custom syntax - you’ll still eventually run into the classic problem where your entire grid rerenders when you change a single cell, but it’s going to be that much trickier to debug because it’s not just regular Typescript.
It's a huge mess in the real world. Without good understanding and discipline, I've seen components which react profiler says re-render 15 times on an initial load because of cascading useEffect hooks.
I lack the expertise to be able to go in and rip it all out, and while I can share articles like this and others such as "You might not need useEffect", there isn't a really obvious way to deal with it all, especially if there are places which genuinely could do with proper memoisation.
The trick is to not worry about useMemo and useCallback because it's rarely an issue unless you have 800 line components or don't compose but rather treat everything as if it was inheritance.
I've got pretty sizeable codebases, less than 5 useEffects ever (nowadays I only have one for a single location where I need to manually poll, the rest is useSyncExternalStore) and I only do useMemo for tables.
Compiler is just an additional performance improvement that's "free" and at least the compiler doesn't make it harder to understand how your code executes because it just works
I do frontend and I also feel uneasy about react. I stand by my assertion that it's trying to solve a hard problem and thus might actually require complex code, but still, the way react structures things with `useFoo` feels like they've really missed some easier ways to do it.
I'm mostly backend but have been fullstack in a few positions and remember expending a bunch of political capital advocating for moving to React when it first came out because it seemed to solve a lot of problems we were having with projects that used Angular or a jQuery patchwork. I still think that it was the right decision at the time, but what React has become today is so much more esoteric and spaghetti-like that I'm not sure having it "win" was the best thing for frontend.
Now I actively go out of my way to avoid doing any fullstack because I spend more time trying to fight against the various fragile towers of incantation-like hooks than just implementing the business logic in a way that feels readable and maintainable. I'm not sure there is a way to remove the complexity either, but so much of modern programming with React just feels unintuitive and convoluted, which is exactly the opposite of how it used to feel when it first came out. I'm not sure how to solve it.
You can still write simple, understandable React. All the weird, obscure hooks they have added in the recent years are opt-in and are often better left for library maintainers. You probably don't even need useMemo or useCallback. That's not to say I don't see developers (mis)using them all the time, but that's self-imposed convolution and you can only blame React as a footgun supplier.
I agree that this is the approach that keeps react apps simple. The biggest problems I see in react code are giant components that read like this:
useState
useState
useState
... // 20 more
useEffect
useEffect
useEffect
... // 20 more
I think a lot of front-end devs don't realize the necessity of custom hooks for composition and modularization. My personal rule is "Never use react hooks (useState, useEffect, etc.) in component code. All react hooks MUST be wrapped by custom hooks." and it makes for MUCH better code:
useSomeData
useOtherData
useMobileView
useScrollToTop
useInteractionLogging
... // 10 more hooks
This is also what makes TanStack so good and popular. It's all just behavior wrapped in hooks.
This I disagree on. Unless the hook is to be reused, just chuck it in your component where it's actually used. Yeah, it's pretty if you can decompose it cleanly. But if you end up with calling useX, and passing a statement function from that to useY etc, it's just a mess figuring out what's going on. Just chuck it all in the component and skip the abstractions.
For very small local state, I can agree. But if you three or more standard hooks that interact together, it’s worth it to wrap it in a custom hook even if it’s going to be in the same file. The semantics will be clearer and working with the code easier. I much prefer to see useFlashyAnimation than its code in the component.
>which is exactly the opposite of how it used to feel when it first came out.
I use React (actually Preact), but the old way, not with "use" anything, no hooks. It's pretty simple, but does a good job at what I need, and I'm doing pretty complex web applications with it.
There's no law saying you must use the bleeding edge of everything, the old React way is still plenty good for most things.
The way they rushed out hooks and everyone jumped on board was a red flag for me. I've used it, I've built stuff with hooks, but it's always seemed like fixing problems by creating more problems, and the hype around it was ridiculous.
I switch to hooks the moment they were in stable. Because functional components are simpler in nature. And the reactive apparatus that hooks are make it simpler to bring external modules. Writing HOC was a pain.
Apparently we are in the minority, but I find hooks great for DRY and composition. My components can be thin and presentational while the business logic is tucked away in neat little packages. Didn't realize how much I disliked class components and HOCs until hooks showed up.
Footguns, learning curve? Sure, but I'll still make that trade.
I was not specifically talking about hooks being "bleeding edge" in 2025. I know when they were first released, and I don't need that techsplained to me.
I'm speaking more generally about the trend of chasing "new, shiny" in front-end. Some people knee-jerk and just go for the "new, shiny" thing as soon as it happens. And other people still use the existing tech, because it's good enough.
I had do frontend for decades and witness the Jquery => Angular.js => React/Vue (Class) => React/Vue (Hook) transition.
And out of them, the react hook is the only one that 'doing it wrong' is easier. A good API interface should have least resistance for the user when doing things correctly. But React Hook almost feels like the complete opposite of this phenomenon. It's complex, comes with tons of foot gun. And yet still get popular?
Wondering if there is something other like this exists in the computer history.
Because it’s very simple. React is only a view layer that promises that your UI will always reflect your state. Bugs is usually when people brings in things that has no place being there. Aka no discipline in separating the view module from everything else.
React is the Git of UIs, the Vulkan, or whatever example you feel more apt for its ways of working, and changes across versions.
Unfortunely, it has won the mindshare game, in many SaaS products I tend to work on, they only provide React and by extension Next.js based SDKs, if I want to use vanila.js, Angular, Vue or whatever, it is on me to make it work, if it is possible at all, given the product expectations on its extension points.
Eventually the "everything must be React" wave is going to pass, hopefully.
The issues with useEffect come from a misunderstanding of how data is meant to flow in react, the fundamental concept on which the whole thing is based.
I was around when React was released (I moved a large Angular app to React), and stopped doing frontend probably a year or two after React went all in on Hooks. I'm well aware of how React is meant to work and how data is supposed to flow in a React application. I agree isn't not complicated. You can understand it perfectly, but the language, API or framework does not make it conducive to easily writing correct code. Where this always ends up is in 3-4 years you end up with a codebase that is slow, complex and hard to reason but has no clear path to fix.
If your large, complex, react app is slow - what is easier? Fixing the data architecture or adding a useMemo? Multiply by that by 1000 and you have a nightmare. The bad thing/escape hatch is vastly simpler than the proper thing, because something about language/framework/api makes doing the proper thing hard or unclear.
> but the language, API or framework does not make it conducive to easily writing correct code
The language is sort of settled. The browser runs JS, so for most projects, it's the right choice.
> If your large, complex, react app is slow - what is easier? Fixing the data architecture or adding a useMemo?
It might be easy to add useMemo, but it doesn't work. People using a tool for the wrong thing because they don't understand it isn't a problem with the tool. It's unfortunate that a tool with a steep learning curve is the "default" for many devs now, but again that isn't a problem with the tool. I don't have any guardrails keeping me from using my saw as a hammer, it just doesn't work.
> because something about language/framework/api makes doing the proper thing hard or unclear.
It's unclear because it was built on functional programming concepts and most devs aren't taught that way. If you are familiar with those concepts, the whole thing is quite simple.
"It works when you do it correctly" isn't really a good argument when discussing UX/DX design in my opinion. Anything that isn't broken will also work when you use it correctly. The question is "how easy to do it correctly?". A poorly designed UX/DX takes user a lot of effort to use it correctly (macdonald's never fixed icecream machine). While a proper designed one allow the user to onboard easily withoud too much training.
It’s very easy to do correctly. Hooks is a translation layer. Move everything that is not directly UI out of the component tree and let hooks bring it in. I’ve seen code where people dump the entire business logic into a component. Which is not good design.
> Adding non-primitive props you get passed into your component to internal dependency arrays is rarely right, because this component has no control over the referential stability of those props.
I think this is wrong? If you memoize a callback with useCallback and that callback uses something from props without putting it in the dependency array, and then the props change & the callback runs, the callback will use the original/stale value from the props and that's almost certainly a bug.
They might be trying to say you just shouldn't use useCallback in that situation, but at best it's very confusingly written there because it sure sounds like it's saying using useCallback but omitting a dependency is acceptable.
IMHO useCallback is still a good idea in those situations presuming you care about the potential needless re-renders (which for a lot of smaller apps probably aren't really a perf issue, so maybe you don't). If component A renders component B and does not memoize its own callback that it passes to B as a prop, that is A's problem, not B's. Memoization is easy to get wrong by forgetting to memoize one spot which cascades downwards like the screenshots example, but that doesn't mean memoization is never helpful or components shouldn't do their best to optimize for performance.
In my experience you're correct yeah - 95% of the time if you use it it should be included in the dependency array, and any edge cases where that's not true you should endeavour to understand and document. There's a 'react-hooks/exhaustive-deps' eslint rule for exactly this purpose.
author here , sorry for the confusion. I did not mean to imply that leaving out the prop from useCallback is better - you _always_ have to adhere to the exhaustive-deps lint rule to avoid stale closures (I have a separate blog post on this topic).
What I meant is that the API design of B, where the hook only works as intended if the consumer A memoizes the input props, is not ideal because A has no way of knowing that they have to memoize it unless they look at the implementation or it’s clearly documented (which it rarely is). It’s not A’s problem because B could’ve used the latest ref pattern (or, in the future, useEffectEvent) to work without passing that burden onto its consumers.
> > Adding non-primitive props you get passed into your component to internal dependency arrays is rarely right, because this component has no control over the referential stability of those props.
> I think this is wrong? If you memoize a callback with useCallback and that callback uses something from props without putting it in the dependency array, and then the props change & the callback runs, the callback will use the original/stale value from the props and that's almost certainly a bug.
The problem is up a level in the tree, not in the component being defined. See the following code block, where they show how the OhNo component is used - the prop is always different.
The parent is the source of truth for the children nodes. Which means don't send anything down that you don't intend to be stable. As the children will (and should) treat it as gospel. That also means that the root component is god for the whole view.
Most source of bugs happens when people ignore this simple fact. Input start from the root and get transformed along the way. Each node can bring things from external, but it should be treated careful as the children down won't care. It's all props to them.
Which also means don't create new objects out of the blue to pass down. When you need to do so, memoize. As the only reason you need to do so is because you want:
- to join two or more props or local state variables
- or to transform the value of a prop or a local state variable (in an immutable fashion, as it should be).
You're making the same argument as the author. As they said, the only two solutions are:
1. Enforce that you're always memoizing everywhere always
2. Memoize every new function and object (and array) and pray everyone else does too
#1 is pretty miserable, #2 is almost always guaranteed to fail.
The mismatch is that you need stable props from the bottom up, and you enforce stable props from the top down. Any oversight in between (even indirectly, like in a hook or external module) creates a bug that appears silently and regresses performance.
The only solution is to have everyone have discipline and respect the convention of React.
> you need stable props from the bottom up, and you enforce stable props from the top down.
You don't need stable props from the bottom up. Component will always react to props change and will returns a new Tree. The function for that are going to be run multiple times and you're not supposed to control when it does. So the answer is to treat everything that comes from the parent as the gospel truth. And any local transformations should be done outside of function calls.
#1 is not needed. Memoization concerns is always local to the component, as long as you ensure that the props you're sending down is referentially stable, you're golden.
#2 is not needed. Again your only concern is the current component. Whatever everyone does elsewhere is their bad practices. Maybe they will cast your carefully constructed type into any or try/catch your thougthful designed errors into oblivion along the way too. The only thing you can do is correct them in PR reviews or fix the code when you happen upon it.
I think one of the most awkward things about React (stemming from its virtue of being just a JavaScript library, as opposed to a framework with some custom DSL that could handle memoization behind the scenes) is that it has to be stated explicitly that non-primitive props should be memoized. React makes it way too easy to pass someProp={[1, 2, 3]}, yet to me it is always a caller’s bug.
Ahh, this is such a pet peeve of mine. I hate how it leaks. A thing I often also encounter in this vein is when you write a useEffect, but the linter forces you to declare everything in the dependency array. I only want the effect to trigger when x changes, and then call function y. But I have to declare y as well, but then if y isn't memoed it will trigger every render. But I don't want that, and I might not even have full control of function y, coming from somewhere else. I could usecallback around it myself first, but that just moves the problem, as I need to maintain the correct closure, or there will be subtle bugs.
A second but unrelated grief with hooks is when you get async updates happening in between renderers where you want to update some state and then do some logic. Let's say I'm waiting for 2 requests to finish. They both should update a value in my state. However, to not lose an update if both happens at the same time, I need to do the setState with a callback to see the latest value. But then, what if I want to trigger something when that new calculated state value is z? I can't do that imperatively inside the callback, and outside it I can't see the new value and don't know that I now should do something additional. I have to do it in a useEffect, but that decouples it too much, the logic belongs where I want to put it, in my state hook. Not in some useeffect wherever. The other option is to misuse refs to also maintain a current state there in addition to the useState. So always use the ref, but set the state in the end to trigger a re render.
Hooks are so unelegant for real problems. I hate how they force your hand to write spaghetti.
> Ahh, this is such a pet peeve of mine. I hate how it leaks. A thing I often also encounter in this vein is when you write a useEffect, but the linter forces you to declare everything in the dependency array. I only want the effect to trigger when x changes, and then call function y. But I have to declare y as well, but then if y isn't memoed it will trigger every render. But I don't want that, and I might not even have full control of function y, coming from somewhere else. I could usecallback around it myself first, but that just moves the problem, as I need to maintain the correct closure, or there will be subtle bugs.
If y never changes it should be declared as a const function outside of the context of the component, then it can be safely included in the dependency array without triggering unnecessary rerenders.
If y does change, you've just created a bug where your component will keep using an old version of y.
Yup, but that's the problem. I don't know, and I don't care, I just want to run my effect when x changes. But I can't, the details of y's implementation must leak.
React is a closed ecosystem. Anything that enters either do through the root component or through hooks (in the functional version). If both x and y enters the ecosystem, they must adapt to the rules, meaning you write what happens when they change. Because the only reason for them to enter is that they are not constant outside React's world (which is the ultimate truth). If they are constant, then just use the reference directly.
Yes, but then you have to do as the author says: wrap every single function in a use callback and declare it dependencies. Otherwise it's unsafe to use for others. Which to me is a broken design. And y in my case may not be my function, either from a library or somewhere else in the application I don't want to modify just because my usage needs it. It should be a compiler thing, not up to the devs, the abstraction feels off.
Often I don't want to run effects, I just want everything calculated properly and then have my components render. I see useEffect() as a bad smell and keep score by how few of them I use (like golf) There really are cases you need it, but people often use them to move state around when they are keeping state in the wrong place.
useEffect should be used only when you want to bring something from outside the React tree which is not constant compared to the tree lifetime (anything that's not part of the UI module), hooking it to the tree lifecycle. useRef is for when you don't care the external thing's mutation. Any other hooks should be used when deriving from things that is already present in the React tree (props, local state, or other memoized variables)
y might be "changing" but being effectively the same function or the same array. There are legitimate cases when you want to just read the latest value of something inside an effect without it being a dependency, and after years of gaslighting about it React seems to agree and useEffectEvent is coming.
I use useEffect the same way and suffer the same grievances. I think it's because useEffect is intended as a way to keep references/state synced, not trigger downstream logic on various interactions. But there's only one useEffect, so it does all the things.
> The other option is to misuse refs to also maintain a current state there in addition to the useState. So always use the ref, but set the state in the end to trigger a re render.
Arugably this isn't a misuse, but just accepting that Reacts state management isn't powerful enough for what you need. Something akin to useRef + useSyncExternalStore is often what developers want.
For example, there's no guarantee that your useEffect will be invoked with the intermediate state you wanted to observe.
> I hope this example shows why I'm passionately against applying memoizations. In my experience, it breaks way too often. It's not worth it. And it adds so much overhead and complexity to all the code we have to read.
React's complexities are almost all self-inflicted footguns created as a byproduct of its design that points the reactive callback to the component function.
This design means that unlike vanilla JS, web components, Vue, Svelte, Solid, etc. where your state explicitly opts in to state changes, React requires component code to explicitly opt out of state changes (making the model much harder to wrap your head around and harder to do well).
This is the actual root cause of all of the underlying complexity of React. In signals-based frameworks/libraries and in vanilla JS with callbacks, there is rarely a need to explicitly memoize.
React is astonishingly clever, the kind of use of functional ideas that is talked about in On Lisp [1] Comparing it to many systems like Microsoft's WPF or Oracle's JavaFX it's an amazingly simple way to implement components. On the happy path life is easy, but go off that path and you're in trouble. Visicalc had real reactive programming in 1979, all that useX stuff is a way to fake it without needing a compiler -- which systems like Vue 5 and Svelte need.
Sometimes I think it would be fun to make an immediate mode rendering framework similar to React -- and given that it can take 10x or more refreshes for a page to be done rendering, it might as well be immediate mode.
But yeah, I figured out the need for signals back in 2006 or so when I was working on a knowledge graph editor based on GWT and having to explain why there were "race conditions" depending on whether or not data was cached. When I got into modern JS around 2017 everything seemed worse than the frameworks I'd built for some very complex (think Figma) applications more than a decade ago.
[1] Of which 80% of the examples don't really need macros
Most of the time it’s not with memoising anything because as it’s pointed out it’s easy to create a component that tries to be performant but its consumer breaks it because it passes a non memoized prop. And it is weird that a library that pushes for composability of UI components demands of you to think of such edge cases.
I have been writing react professionally in large codebases for 5 years now and I am quite disappointed by the course react followed. It became more complex but not more powerful, it introduced features that feel like bandaid fixes rather than new tools that open up new opportunities, it broke its own rules and patterns (the new “use”, the “useForm”) and it aggressively pushed for server side rendering, alienating hobbyists and small scale users who enjoyed SPAs.
Like another user mentioned, I am irritated by the linter rule for full exhaustive dependencies in effects, because it takes control away and also forces me to memoise everything. Or not, as the article points out. The library is not easy to build with anymore and it’s comfortably sitting at the point where it’s used everywhere and people are getting tired of its quirks. Which, if we are lucky, means it prepares the way for something better.
I would describe the reasons to useMemo or useCallback as follows, in order of decreasing importance: 1) referential equality of non-primitives (any object, function, etc.) passed downstream as props and 2) expensive computation. The first one is for me non-optional, and I’ll focus on that (as the second one is self-explanatory).
Once you know about the reactive render cycle, it becomes a second nature to never create any object other than via a memo function with declared dependencies. Anything else—any instance of something like <Component someProp={[1, 2, 3]} />—is, to me, just a bug on the caller side, not something to be accommodated.
Furthermore, ensuring referential equality has benefits beyond avoiding re-renders (which I would acknowledge should generally be harmless, although are in practice a source of hard to spot issues and loops on occasion).
— First, it serves as a clarity aid and documentation for yourself. It helps maintain a mental graph like “caller-provided props A and B are used to compute prop C for a downstream component”. Code organized this way tends to have self-evident refactor routes, and logic naturally localizes into cohesive sub-blocks with clearly defined inputs and outputs.
— Second, and it is obviously an ES runtime thing, but it is simply weird to have to reason about objects being essentially the same but different—and if memoization allows me to not have to do it and, with some discipline, make it safe to assume that (() => true) is the same as (() => true), why pass on that opportunity?
That said, now that I see an alternative take on this, I will make sure to document my approach in the development guidelines section of the README.
> it is obviously an ES runtime thing, but it is simply weird to have to reason about objects being essentially the same but different—and if memoization allows me to not have to do it and, with some discipline, make it safe to assume that (() => true) is the same as (() => true), why pass on that opportunity?
it sucks both ways - the default is that stuff is different if it looks the same so you can't not do anything, but the only solution is both a lot of work and really easy to mess up completely. The TC39 Composites proposal[1] would be a great step in the right direction, but it will take years in the best scenario, there's really no guarantee that React will take advantage of it, and it still leaves out a lot of cases (Dates, to name one)
> the only solution is both a lot of work and really easy to mess up
I think the other benefit is enough to justify the effort, though. Essentially, you don’t just make up objects, but instead you define each object’s constructor and all of its inputs. This makes it natural to keep logic contained and easy to shuffle things around when refactoring!
yeah I completely agree, I think there's a whole logic of reasons to using useEffect/useCallback/useMemo that the post didn't acknowledge.
personally, I want to have a comprehensive understanding of exactly when and why each of my components re-renders, and to do that I use hooks and design around referential equality. like you said, it's a kind of documentation, more about organization than performance.
not to say that this way is intrinsically better! just that the style is appealing for its own reasons
I think folks were too quick to throw out the good things about redux when hooks came out. Namely “dumb” presentational components that had no logic and abstractions for wiring up data and actions (mapStateToProps and mapDispatchToProps).
Sure reducers could be a bear (and asynchronous actions were hard), but the easily testable and portable logic-less views were really nice to work with.
Having worked in a number of codebases from React’s earliest days until now, I see echos of the bad old mixin days in the usage of hooks.
I'm the primary Redux maintainer. FWIW, `connect` still exists and we have no plans to remove it, but it's also _very_ complicated internally and we honestly don't want people using it today. If I _could_ remove it without breaking user apps I would. `useSelector` is a drastically simpler implementation, better app performance, and smaller bundle size.
I do get what you mean about the conceptual separation, although something about the HOC approach also led to a lot of historical user confusion about "where _are_ my props coming from?".
As for writing the rest of your Redux logic, our modern Redux Toolkit package has addressed the historical concerns about boilerplate and other pain points:
People say "don't just blindly use React.memo, fix slow renders first." But like... with referential identity being so cheap, blindly applying React.memo should be a huge win?
CostOfCache is pretty cheap compared to the CostPreCache IMO... CostWhenHittingCache is very low... and CacheMissPercentage is also probably pretty low in your typical React component that has more than 2 children node involved. Mathematically it feels like a no-brainer!
"Fix your slow renders first" just feels off. Yes you want to fix slow first renders! You also want to avoid wasting render cycles. These are distinct issues from my view. What am I missing? Why shouldn't React "just" do useMemo by default?
> blindly applying React.memo should be a huge win
That’s what the react compiler does, and it’s a good idea in that case because the compiler knows how to do it correctly, for _everything_. When humans try to do it, they will likely get it wrong (see the real world example in the article, this is the norm imo).
I still don't 100% understand why `React.memo` is not applied by default, wrapping every single component in a `memo` is crazy overhead and non-trivial to enforce codebase-wide.
Sure performance is a concern but is recursive re-rendering really cheaper than memoing all props?
Someone used to argument with me that "it will make render pass that use external value works if it always renders".
But I wonder should it even work at first place? It feels like you are covering the problem instead of actually fix it. Make broken things broken is a lot healthier for the code base in my opinion.
Immediately find out things to be broken is 100x better than find out it suddenly not works and can't even figure out why is it broken months later in my opinion.
Use external value in Vue is very noticeable in contrary to React. Because it will definitely not work even once. The in component computed or component itself both enforce memorize . Render will be skipped completely it you used external values there so the framework did not notice anything is changed.
Whenever I see articles like that, I can't help but feel the migration from Class components to functional components with hooks was a big complexity jump for little gain.
I've been toying with an idea of a pattern. I'm curious as to if it has a name. I'll write a blog post once I have an app using it. In the meantime, it's (roughly):
- No Hooks.
- Don't try to sync any state. E.g. keep a dom element as the source of truth but provide access to it globally, let's call this external state.
- Keep all other state in one (root) immutable global object.
- Make a tree of derived state coming from the root state and other globally available state. (These are like selectors and those computations should memoized similar to re-select)
- Now imagine at any point in time you have another tree; the dom tree. If you try to make the state tree match to dom tree you get prop drilling.
- Instead, flip the dom tree upside down and the leaves get their data out of the memoized global state.
- Parent components never pass props to children, the rendered children are passed as props to the parent.
You end up with a diamond with all state on the top and the root of the dom tree on the bottom.
One note, is that the tree is lazy as well as memoized, there's potentially many computations that don't actually need to be computed at all at any given time.
You need something like Rx to make an observable of the root state, some other tools to know when the external state changes. Some memoization library, and the react is left with just the dom diffing, but at that point you should swap out to a simpler alternative.
you can do something like this with most global state libraries, Jotai to name one. But very soon you'll see that you need effects there, so you'll need that global state solution to be rock solid in that aspect
I don't have a problem with needing to memoize props passed to child components for their memoization to work.
If your parent component doesn't need the optimization, you don't use it. If it does need it, your intention for using useMemo and useCallback us obvious. It doesn't make your code more confusing inherently.
The article paints it as this odd way of optimizing the component tree that creates an invisible link between the parent and child - but it's the way to prevent unnecessary renders, and for that reason I think it's pretty self-documenting. If I'm using useMemo and useCallback, it's because I am optimizing renders.
At worst it's unnecessary - which is the point of the article - but I suppose I don't care as much about having unnecessary calls to useMemo and useCallback and that's the crux of it. Even if it's not impacting my renders now, it could in the future, and I don't think it comes at much cost.
I don't think it's an egregious level of indirection either. You're moving your callbacks to the top of the same function where all of your state and props are already.
Thank you, that was the example I needed to hear to see why this could be an issue.
I will still say though, I have not actually had this happen to me yet with all the years of using hooks. Generally when I'm fetching when X prop changes, it's not in response to functions or objects, and I guess if it's ever happened it's been fixed and never broke or hasn't caused problems.
Not to say it isn't an issue - it is - but the number and degree of issues I saw with lifecycle functions was much worse. That was with a less experienced team, so it could just be bias.
The useRef pattern seem like a code smell, maybe because I had some troubles with refs over the years.
Things were simpler when we had lifecycle methods to manage some of this things. But I`m sure that the next version of react will change everything and make us come up with more patters to try to fix the same problem again...
A useless machine or useless box is a device whose only function is to turn itself off. The best-known useless machines are those inspired by Marvin Minsky's design, in which the device's sole function is to switch itself off by operating its own "off" switch. Such machines were popularized commercially in the 1960s, sold as an amusing engineering hack, or as a joke.
More elaborate devices and some novelty toys, which have an obvious entertainment function, have been based on these simple useless machines.
History
The Italian artist Bruno Munari began building "useless machines" (macchine inutili) in the 1930s. He was a "third generation" Futurist and did not share the first generation's boundless enthusiasm for technology but sought to counter the threats of a world under machine rule by building machines that were artistic and unproductive.
A wooden "useless box"
The version of the useless machine that became famous in information theory (basically a box with a simple switch which, when turned "on", causes a hand or lever to appear from inside the box that switches the machine "off" before disappearing inside the box again) appears to have been invented by MIT professor and artificial intelligence pioneer Marvin Minsky, while he was a graduate student at Bell Labs in 1952. Minsky dubbed his invention the "ultimate machine", but this nomenclature did not catch on. The device has also been called the "Leave Me Alone Box".
Minsky's mentor at Bell Labs, information theory pioneer Claude Shannon (who later became an MIT professor himself), made his own versions of the machine. He kept one on his desk, where science fiction author Arthur C. Clarke saw it. Clarke later wrote, "There is something unspeakably sinister about a machine that does nothing—absolutely nothing—except switch itself off", and he was fascinated by the concept.
Minsky also invented a "gravity machine" that would ring a bell if the gravitational constant were to change, a theoretical possibility that is not expected to occur in the foreseeable future.
This reminds me of a coworker that was writing a program for some late 80's based DSP accelerator. He tried compiling the thing and nothing happened for a few minutes and then it printed out 'too many errors'.
When he went to edit his program it had been deleted. So he rewrote it and tried compiling it again and found the compiler had deleted itself too.
I like the idea of a program that takes increasingly dire steps to prevent you from compiling it each time you try.
I remember when React api was unique, then it comes up with some idea (hooks) and other front-end frameworks copied it, that's like confirmation that the idea is crap.
React is easy, once you understand why
{} !== {}
and that it uses reference equality as a proxy for value equality.
This is why the absolute simplest thing to do is to just memoize all the things (see https://attardi.org/why-we-memo-all-the-things/ for a deeper dive into this approach).
I know that the conventional wisdom (e.g. https://kentcdodds.com/blog/usememo-and-usecallback) is not to do that, but it's worth noting that Kent doesn't actually bring receipts for his claims, and also the discussion is typically limited to toy examples, not huge applications that have the potential for huge cascading renders when anything changes. In my own profiling of real world applications I have found that memoizing everything is not actually a performance regression. And then also consider the React Compiler, which of course does the same, at an even more fine-grained level than any human could be bothered to consistently do by hand.
I think we lack the primitives to understand and discuss messy complicated real world frontends. Something about UI state and its coupling to servers, plus time, plus Conway's Law, causes a real messy situation that appears to be much easier to avoid on the backend than on the frontend.
This creates a massive gulf between beautiful simple frontend toy examples and the reality of applications made with those same frameworks when deployed for multiple years and product cycles. I think it is a big part of what fuels shiny framework adoption. You think: "yes, this here is what will resolve the complexity and pains I see in my work codebase", and this is bolstered by your carefully curated personal side projects.
I'm not in frontend mainly, but this article, and another short I watched about how useEffect is almost always wrong makes me feel uneasy about React. It's insidiously complex.
A bunch of `useCallbacks` are rendered ineffective because the top of the chain wasn't properly computed. And the fix isn't `useCallback`, its to rearchitect the tree. This feels wrong - either the API, the framework, or the language isn't powerful enough to ensure that someone does the right and performant thing. It seems like the new React Compiler tries to fix this - but I'm surprised React Compiler is a thing that needs to be built.
Yes, but in all fairness that’s React 16.8+ (2019)
Before that you could write:
The upcoming `useEffectEvent` will make React even more cryptic and convoluted.React.Component still exists. And most `useLatest` implementations will get you the advantage of `useEffectEvent` today.
If you really wanted to take a jab at React people, show them
> This feels wrong - either the API, the framework, or the language isn't powerful enough to ensure that someone does the right and performant thing.
It's the language. React uses a bunch of magic methods and requires you to follow rules about when to call them, with limited guardrails, because if you try to make developers do this properly (with higher-kinded types and monads) they throw their toys out of the pram. Our culture prefers "there are a bunch of subtle rules you have to follow and if you break them you'll get weird bugs or performance issues" to "there are a bunch of explicit rules you have to follow and if you break them your code won't compile", sadly.
Is there something out there (in TypeScript, preferably) that does things in the way that you’re describing?
Svelte is a lot closer to that latter style, there are a lot of React weirdnesses that just aren’t possible in Svelte (because it’s a compiler, not a framework)
I'd rather break my hands than use Svelte instead of React again. It comes with so many problems of itself. Summarized in a single sentence though it would be: It's more common than you'd like it to be, that your UI doesn't actually reflect the state of your application.
I was considering investing in learning Svelte to avoid some of the React quirkiness, but this is not very promising. Can you share a bit more? Trading off a set of quirky annoying things for an equivalent or worse one would be suboptimal.
Not typescript, but elm essentially promises that. Unfortunately it's been pretty much abandoned while barely scratching the surface of javascripts api (and had drama when the community attempted to make their own native code)
I use Vue (and had use React for a while) and Vue is much more intuitive to me.
In my experience it just abstracts all the same issues behind its own custom syntax - you’ll still eventually run into the classic problem where your entire grid rerenders when you change a single cell, but it’s going to be that much trickier to debug because it’s not just regular Typescript.
TypeScript isn't powerful enough, but take a look at how the PureScript bindings do it.
It's a huge mess in the real world. Without good understanding and discipline, I've seen components which react profiler says re-render 15 times on an initial load because of cascading useEffect hooks.
I lack the expertise to be able to go in and rip it all out, and while I can share articles like this and others such as "You might not need useEffect", there isn't a really obvious way to deal with it all, especially if there are places which genuinely could do with proper memoisation.
The trick is to not worry about useMemo and useCallback because it's rarely an issue unless you have 800 line components or don't compose but rather treat everything as if it was inheritance.
I've got pretty sizeable codebases, less than 5 useEffects ever (nowadays I only have one for a single location where I need to manually poll, the rest is useSyncExternalStore) and I only do useMemo for tables.
Compiler is just an additional performance improvement that's "free" and at least the compiler doesn't make it harder to understand how your code executes because it just works
I do frontend and I also feel uneasy about react. I stand by my assertion that it's trying to solve a hard problem and thus might actually require complex code, but still, the way react structures things with `useFoo` feels like they've really missed some easier ways to do it.
I'm mostly backend but have been fullstack in a few positions and remember expending a bunch of political capital advocating for moving to React when it first came out because it seemed to solve a lot of problems we were having with projects that used Angular or a jQuery patchwork. I still think that it was the right decision at the time, but what React has become today is so much more esoteric and spaghetti-like that I'm not sure having it "win" was the best thing for frontend.
Now I actively go out of my way to avoid doing any fullstack because I spend more time trying to fight against the various fragile towers of incantation-like hooks than just implementing the business logic in a way that feels readable and maintainable. I'm not sure there is a way to remove the complexity either, but so much of modern programming with React just feels unintuitive and convoluted, which is exactly the opposite of how it used to feel when it first came out. I'm not sure how to solve it.
You can still write simple, understandable React. All the weird, obscure hooks they have added in the recent years are opt-in and are often better left for library maintainers. You probably don't even need useMemo or useCallback. That's not to say I don't see developers (mis)using them all the time, but that's self-imposed convolution and you can only blame React as a footgun supplier.
I ise hooks all the time, mostly because I write custom hooks that wraps everything external to my view. Makes for a simpler conponent code.
I agree that this is the approach that keeps react apps simple. The biggest problems I see in react code are giant components that read like this:
useState
useState
useState
... // 20 more
useEffect
useEffect
useEffect
... // 20 more
I think a lot of front-end devs don't realize the necessity of custom hooks for composition and modularization. My personal rule is "Never use react hooks (useState, useEffect, etc.) in component code. All react hooks MUST be wrapped by custom hooks." and it makes for MUCH better code:
useSomeData
useOtherData
useMobileView
useScrollToTop
useInteractionLogging
... // 10 more hooks
This is also what makes TanStack so good and popular. It's all just behavior wrapped in hooks.
This I disagree on. Unless the hook is to be reused, just chuck it in your component where it's actually used. Yeah, it's pretty if you can decompose it cleanly. But if you end up with calling useX, and passing a statement function from that to useY etc, it's just a mess figuring out what's going on. Just chuck it all in the component and skip the abstractions.
For very small local state, I can agree. But if you three or more standard hooks that interact together, it’s worth it to wrap it in a custom hook even if it’s going to be in the same file. The semantics will be clearer and working with the code easier. I much prefer to see useFlashyAnimation than its code in the component.
>which is exactly the opposite of how it used to feel when it first came out.
I use React (actually Preact), but the old way, not with "use" anything, no hooks. It's pretty simple, but does a good job at what I need, and I'm doing pretty complex web applications with it.
There's no law saying you must use the bleeding edge of everything, the old React way is still plenty good for most things.
The way they rushed out hooks and everyone jumped on board was a red flag for me. I've used it, I've built stuff with hooks, but it's always seemed like fixing problems by creating more problems, and the hype around it was ridiculous.
I switch to hooks the moment they were in stable. Because functional components are simpler in nature. And the reactive apparatus that hooks are make it simpler to bring external modules. Writing HOC was a pain.
Hooks are a good translation layer.
Apparently we are in the minority, but I find hooks great for DRY and composition. My components can be thin and presentational while the business logic is tucked away in neat little packages. Didn't realize how much I disliked class components and HOCs until hooks showed up.
Footguns, learning curve? Sure, but I'll still make that trade.
Hooks were introduced in early 2019, they're hardly bleeding edge
I was not specifically talking about hooks being "bleeding edge" in 2025. I know when they were first released, and I don't need that techsplained to me.
I'm speaking more generally about the trend of chasing "new, shiny" in front-end. Some people knee-jerk and just go for the "new, shiny" thing as soon as it happens. And other people still use the existing tech, because it's good enough.
I feel the same way about vue switching to hooks. What is the exact problem we're solving?
I had do frontend for decades and witness the Jquery => Angular.js => React/Vue (Class) => React/Vue (Hook) transition.
And out of them, the react hook is the only one that 'doing it wrong' is easier. A good API interface should have least resistance for the user when doing things correctly. But React Hook almost feels like the complete opposite of this phenomenon. It's complex, comes with tons of foot gun. And yet still get popular?
Wondering if there is something other like this exists in the computer history.
Because it’s very simple. React is only a view layer that promises that your UI will always reflect your state. Bugs is usually when people brings in things that has no place being there. Aka no discipline in separating the view module from everything else.
React is the Git of UIs, the Vulkan, or whatever example you feel more apt for its ways of working, and changes across versions.
Unfortunely, it has won the mindshare game, in many SaaS products I tend to work on, they only provide React and by extension Next.js based SDKs, if I want to use vanila.js, Angular, Vue or whatever, it is on me to make it work, if it is possible at all, given the product expectations on its extension points.
Eventually the "everything must be React" wave is going to pass, hopefully.
The issues with useEffect come from a misunderstanding of how data is meant to flow in react, the fundamental concept on which the whole thing is based.
It’s not complicated at all.
I was around when React was released (I moved a large Angular app to React), and stopped doing frontend probably a year or two after React went all in on Hooks. I'm well aware of how React is meant to work and how data is supposed to flow in a React application. I agree isn't not complicated. You can understand it perfectly, but the language, API or framework does not make it conducive to easily writing correct code. Where this always ends up is in 3-4 years you end up with a codebase that is slow, complex and hard to reason but has no clear path to fix.
If your large, complex, react app is slow - what is easier? Fixing the data architecture or adding a useMemo? Multiply by that by 1000 and you have a nightmare. The bad thing/escape hatch is vastly simpler than the proper thing, because something about language/framework/api makes doing the proper thing hard or unclear.
> but the language, API or framework does not make it conducive to easily writing correct code
The language is sort of settled. The browser runs JS, so for most projects, it's the right choice.
> If your large, complex, react app is slow - what is easier? Fixing the data architecture or adding a useMemo?
It might be easy to add useMemo, but it doesn't work. People using a tool for the wrong thing because they don't understand it isn't a problem with the tool. It's unfortunate that a tool with a steep learning curve is the "default" for many devs now, but again that isn't a problem with the tool. I don't have any guardrails keeping me from using my saw as a hammer, it just doesn't work.
> because something about language/framework/api makes doing the proper thing hard or unclear.
It's unclear because it was built on functional programming concepts and most devs aren't taught that way. If you are familiar with those concepts, the whole thing is quite simple.
We simply have different boundaries on whats a tool problem vs a developer problem.
If the tool is easily misused, the tool is wrong. i.e. "You're holding it wrong" is a problem with the phone, not the user.
>The language is sort of settled. The browser runs JS, so for most projects, it's the right choice.
TypeScript exists. Something could be made that compiles to JS that makes this easier.
"It works when you do it correctly" isn't really a good argument when discussing UX/DX design in my opinion. Anything that isn't broken will also work when you use it correctly. The question is "how easy to do it correctly?". A poorly designed UX/DX takes user a lot of effort to use it correctly (macdonald's never fixed icecream machine). While a proper designed one allow the user to onboard easily withoud too much training.
> The question is "how easy to do it correctly?"
It’s quite easy. The complaints people have about it are due to friction from using it wrong.
It’s very easy to do correctly. Hooks is a translation layer. Move everything that is not directly UI out of the component tree and let hooks bring it in. I’ve seen code where people dump the entire business logic into a component. Which is not good design.
> Adding non-primitive props you get passed into your component to internal dependency arrays is rarely right, because this component has no control over the referential stability of those props.
I think this is wrong? If you memoize a callback with useCallback and that callback uses something from props without putting it in the dependency array, and then the props change & the callback runs, the callback will use the original/stale value from the props and that's almost certainly a bug.
They might be trying to say you just shouldn't use useCallback in that situation, but at best it's very confusingly written there because it sure sounds like it's saying using useCallback but omitting a dependency is acceptable.
IMHO useCallback is still a good idea in those situations presuming you care about the potential needless re-renders (which for a lot of smaller apps probably aren't really a perf issue, so maybe you don't). If component A renders component B and does not memoize its own callback that it passes to B as a prop, that is A's problem, not B's. Memoization is easy to get wrong by forgetting to memoize one spot which cascades downwards like the screenshots example, but that doesn't mean memoization is never helpful or components shouldn't do their best to optimize for performance.
In my experience you're correct yeah - 95% of the time if you use it it should be included in the dependency array, and any edge cases where that's not true you should endeavour to understand and document. There's a 'react-hooks/exhaustive-deps' eslint rule for exactly this purpose.
author here , sorry for the confusion. I did not mean to imply that leaving out the prop from useCallback is better - you _always_ have to adhere to the exhaustive-deps lint rule to avoid stale closures (I have a separate blog post on this topic).
What I meant is that the API design of B, where the hook only works as intended if the consumer A memoizes the input props, is not ideal because A has no way of knowing that they have to memoize it unless they look at the implementation or it’s clearly documented (which it rarely is). It’s not A’s problem because B could’ve used the latest ref pattern (or, in the future, useEffectEvent) to work without passing that burden onto its consumers.
> > Adding non-primitive props you get passed into your component to internal dependency arrays is rarely right, because this component has no control over the referential stability of those props.
> I think this is wrong? If you memoize a callback with useCallback and that callback uses something from props without putting it in the dependency array, and then the props change & the callback runs, the callback will use the original/stale value from the props and that's almost certainly a bug.
The problem is up a level in the tree, not in the component being defined. See the following code block, where they show how the OhNo component is used - the prop is always different.
The parent is the source of truth for the children nodes. Which means don't send anything down that you don't intend to be stable. As the children will (and should) treat it as gospel. That also means that the root component is god for the whole view.
Most source of bugs happens when people ignore this simple fact. Input start from the root and get transformed along the way. Each node can bring things from external, but it should be treated careful as the children down won't care. It's all props to them.
Which also means don't create new objects out of the blue to pass down. When you need to do so, memoize. As the only reason you need to do so is because you want:
- to join two or more props or local state variables
- or to transform the value of a prop or a local state variable (in an immutable fashion, as it should be).
- or both.
You're making the same argument as the author. As they said, the only two solutions are:
1. Enforce that you're always memoizing everywhere always
2. Memoize every new function and object (and array) and pray everyone else does too
#1 is pretty miserable, #2 is almost always guaranteed to fail.
The mismatch is that you need stable props from the bottom up, and you enforce stable props from the top down. Any oversight in between (even indirectly, like in a hook or external module) creates a bug that appears silently and regresses performance.
The only solution is to have everyone have discipline and respect the convention of React.
> you need stable props from the bottom up, and you enforce stable props from the top down.
You don't need stable props from the bottom up. Component will always react to props change and will returns a new Tree. The function for that are going to be run multiple times and you're not supposed to control when it does. So the answer is to treat everything that comes from the parent as the gospel truth. And any local transformations should be done outside of function calls.
#1 is not needed. Memoization concerns is always local to the component, as long as you ensure that the props you're sending down is referentially stable, you're golden.
#2 is not needed. Again your only concern is the current component. Whatever everyone does elsewhere is their bad practices. Maybe they will cast your carefully constructed type into any or try/catch your thougthful designed errors into oblivion along the way too. The only thing you can do is correct them in PR reviews or fix the code when you happen upon it.
I think one of the most awkward things about React (stemming from its virtue of being just a JavaScript library, as opposed to a framework with some custom DSL that could handle memoization behind the scenes) is that it has to be stated explicitly that non-primitive props should be memoized. React makes it way too easy to pass someProp={[1, 2, 3]}, yet to me it is always a caller’s bug.
Hence react compiler
Does it automatically memo non-primitives?
IIRC that's the idea
Ahh, this is such a pet peeve of mine. I hate how it leaks. A thing I often also encounter in this vein is when you write a useEffect, but the linter forces you to declare everything in the dependency array. I only want the effect to trigger when x changes, and then call function y. But I have to declare y as well, but then if y isn't memoed it will trigger every render. But I don't want that, and I might not even have full control of function y, coming from somewhere else. I could usecallback around it myself first, but that just moves the problem, as I need to maintain the correct closure, or there will be subtle bugs.
A second but unrelated grief with hooks is when you get async updates happening in between renderers where you want to update some state and then do some logic. Let's say I'm waiting for 2 requests to finish. They both should update a value in my state. However, to not lose an update if both happens at the same time, I need to do the setState with a callback to see the latest value. But then, what if I want to trigger something when that new calculated state value is z? I can't do that imperatively inside the callback, and outside it I can't see the new value and don't know that I now should do something additional. I have to do it in a useEffect, but that decouples it too much, the logic belongs where I want to put it, in my state hook. Not in some useeffect wherever. The other option is to misuse refs to also maintain a current state there in addition to the useState. So always use the ref, but set the state in the end to trigger a re render.
Hooks are so unelegant for real problems. I hate how they force your hand to write spaghetti.
> Ahh, this is such a pet peeve of mine. I hate how it leaks. A thing I often also encounter in this vein is when you write a useEffect, but the linter forces you to declare everything in the dependency array. I only want the effect to trigger when x changes, and then call function y. But I have to declare y as well, but then if y isn't memoed it will trigger every render. But I don't want that, and I might not even have full control of function y, coming from somewhere else. I could usecallback around it myself first, but that just moves the problem, as I need to maintain the correct closure, or there will be subtle bugs.
If y never changes it should be declared as a const function outside of the context of the component, then it can be safely included in the dependency array without triggering unnecessary rerenders.
If y does change, you've just created a bug where your component will keep using an old version of y.
Yup, but that's the problem. I don't know, and I don't care, I just want to run my effect when x changes. But I can't, the details of y's implementation must leak.
Must it?
React is a closed ecosystem. Anything that enters either do through the root component or through hooks (in the functional version). If both x and y enters the ecosystem, they must adapt to the rules, meaning you write what happens when they change. Because the only reason for them to enter is that they are not constant outside React's world (which is the ultimate truth). If they are constant, then just use the reference directly.
Yes, but then you have to do as the author says: wrap every single function in a use callback and declare it dependencies. Otherwise it's unsafe to use for others. Which to me is a broken design. And y in my case may not be my function, either from a library or somewhere else in the application I don't want to modify just because my usage needs it. It should be a compiler thing, not up to the devs, the abstraction feels off.
Often I don't want to run effects, I just want everything calculated properly and then have my components render. I see useEffect() as a bad smell and keep score by how few of them I use (like golf) There really are cases you need it, but people often use them to move state around when they are keeping state in the wrong place.
useEffect should be used only when you want to bring something from outside the React tree which is not constant compared to the tree lifetime (anything that's not part of the UI module), hooking it to the tree lifecycle. useRef is for when you don't care the external thing's mutation. Any other hooks should be used when deriving from things that is already present in the React tree (props, local state, or other memoized variables)
y might be "changing" but being effectively the same function or the same array. There are legitimate cases when you want to just read the latest value of something inside an effect without it being a dependency, and after years of gaslighting about it React seems to agree and useEffectEvent is coming.
I might be an outlier here, but I really miss declaring component properties as actual object properties.
Components seem extremely similar to classic OO, why can't I use the same syntax.
React with hooks should have been a new framework. The only reason it wasn't was because of branding. It's an entirely different paradigm.
I use useEffect the same way and suffer the same grievances. I think it's because useEffect is intended as a way to keep references/state synced, not trigger downstream logic on various interactions. But there's only one useEffect, so it does all the things.
> The other option is to misuse refs to also maintain a current state there in addition to the useState. So always use the ref, but set the state in the end to trigger a re render.
Arugably this isn't a misuse, but just accepting that Reacts state management isn't powerful enough for what you need. Something akin to useRef + useSyncExternalStore is often what developers want.
For example, there's no guarantee that your useEffect will be invoked with the intermediate state you wanted to observe.
This design means that unlike vanilla JS, web components, Vue, Svelte, Solid, etc. where your state explicitly opts in to state changes, React requires component code to explicitly opt out of state changes (making the model much harder to wrap your head around and harder to do well).
This is the actual root cause of all of the underlying complexity of React. In signals-based frameworks/libraries and in vanilla JS with callbacks, there is rarely a need to explicitly memoize.
For the curious, I have some code samples here that make it more obvious: https://chrlschn.dev/blog/2025/01/the-inverted-reactivity-mo...
React is astonishingly clever, the kind of use of functional ideas that is talked about in On Lisp [1] Comparing it to many systems like Microsoft's WPF or Oracle's JavaFX it's an amazingly simple way to implement components. On the happy path life is easy, but go off that path and you're in trouble. Visicalc had real reactive programming in 1979, all that useX stuff is a way to fake it without needing a compiler -- which systems like Vue 5 and Svelte need.
Sometimes I think it would be fun to make an immediate mode rendering framework similar to React -- and given that it can take 10x or more refreshes for a page to be done rendering, it might as well be immediate mode.
But yeah, I figured out the need for signals back in 2006 or so when I was working on a knowledge graph editor based on GWT and having to explain why there were "race conditions" depending on whether or not data was cached. When I got into modern JS around 2017 everything seemed worse than the frameworks I'd built for some very complex (think Figma) applications more than a decade ago.
[1] Of which 80% of the examples don't really need macros
Most of the time it’s not with memoising anything because as it’s pointed out it’s easy to create a component that tries to be performant but its consumer breaks it because it passes a non memoized prop. And it is weird that a library that pushes for composability of UI components demands of you to think of such edge cases.
I have been writing react professionally in large codebases for 5 years now and I am quite disappointed by the course react followed. It became more complex but not more powerful, it introduced features that feel like bandaid fixes rather than new tools that open up new opportunities, it broke its own rules and patterns (the new “use”, the “useForm”) and it aggressively pushed for server side rendering, alienating hobbyists and small scale users who enjoyed SPAs.
Like another user mentioned, I am irritated by the linter rule for full exhaustive dependencies in effects, because it takes control away and also forces me to memoise everything. Or not, as the article points out. The library is not easy to build with anymore and it’s comfortably sitting at the point where it’s used everywhere and people are getting tired of its quirks. Which, if we are lucky, means it prepares the way for something better.
I would describe the reasons to useMemo or useCallback as follows, in order of decreasing importance: 1) referential equality of non-primitives (any object, function, etc.) passed downstream as props and 2) expensive computation. The first one is for me non-optional, and I’ll focus on that (as the second one is self-explanatory).
Once you know about the reactive render cycle, it becomes a second nature to never create any object other than via a memo function with declared dependencies. Anything else—any instance of something like <Component someProp={[1, 2, 3]} />—is, to me, just a bug on the caller side, not something to be accommodated.
Furthermore, ensuring referential equality has benefits beyond avoiding re-renders (which I would acknowledge should generally be harmless, although are in practice a source of hard to spot issues and loops on occasion).
— First, it serves as a clarity aid and documentation for yourself. It helps maintain a mental graph like “caller-provided props A and B are used to compute prop C for a downstream component”. Code organized this way tends to have self-evident refactor routes, and logic naturally localizes into cohesive sub-blocks with clearly defined inputs and outputs.
— Second, and it is obviously an ES runtime thing, but it is simply weird to have to reason about objects being essentially the same but different—and if memoization allows me to not have to do it and, with some discipline, make it safe to assume that (() => true) is the same as (() => true), why pass on that opportunity?
That said, now that I see an alternative take on this, I will make sure to document my approach in the development guidelines section of the README.
> it is obviously an ES runtime thing, but it is simply weird to have to reason about objects being essentially the same but different—and if memoization allows me to not have to do it and, with some discipline, make it safe to assume that (() => true) is the same as (() => true), why pass on that opportunity?
it sucks both ways - the default is that stuff is different if it looks the same so you can't not do anything, but the only solution is both a lot of work and really easy to mess up completely. The TC39 Composites proposal[1] would be a great step in the right direction, but it will take years in the best scenario, there's really no guarantee that React will take advantage of it, and it still leaves out a lot of cases (Dates, to name one)
[1]: https://github.com/tc39/proposal-composites
> the only solution is both a lot of work and really easy to mess up
I think the other benefit is enough to justify the effort, though. Essentially, you don’t just make up objects, but instead you define each object’s constructor and all of its inputs. This makes it natural to keep logic contained and easy to shuffle things around when refactoring!
yeah I completely agree, I think there's a whole logic of reasons to using useEffect/useCallback/useMemo that the post didn't acknowledge.
personally, I want to have a comprehensive understanding of exactly when and why each of my components re-renders, and to do that I use hooks and design around referential equality. like you said, it's a kind of documentation, more about organization than performance.
not to say that this way is intrinsically better! just that the style is appealing for its own reasons
I think folks were too quick to throw out the good things about redux when hooks came out. Namely “dumb” presentational components that had no logic and abstractions for wiring up data and actions (mapStateToProps and mapDispatchToProps).
Sure reducers could be a bear (and asynchronous actions were hard), but the easily testable and portable logic-less views were really nice to work with.
Having worked in a number of codebases from React’s earliest days until now, I see echos of the bad old mixin days in the usage of hooks.
I'm the primary Redux maintainer. FWIW, `connect` still exists and we have no plans to remove it, but it's also _very_ complicated internally and we honestly don't want people using it today. If I _could_ remove it without breaking user apps I would. `useSelector` is a drastically simpler implementation, better app performance, and smaller bundle size.
I do get what you mean about the conceptual separation, although something about the HOC approach also led to a lot of historical user confusion about "where _are_ my props coming from?".
As for writing the rest of your Redux logic, our modern Redux Toolkit package has addressed the historical concerns about boilerplate and other pain points:
- https://redux.js.org/introduction/why-rtk-is-redux-today
- https://redux.js.org/tutorials/essentials/part-2-app-structu...
Hooks turn out to be a leaky and contagious abstraction.
People say "don't just blindly use React.memo, fix slow renders first." But like... with referential identity being so cheap, blindly applying React.memo should be a huge win?
CostPostCache = CostOfCache + CacheHitPercentage * CostWhenHittingCache + CacheMissPercentage * CostPreCache
CostOfCache is pretty cheap compared to the CostPreCache IMO... CostWhenHittingCache is very low... and CacheMissPercentage is also probably pretty low in your typical React component that has more than 2 children node involved. Mathematically it feels like a no-brainer!
"Fix your slow renders first" just feels off. Yes you want to fix slow first renders! You also want to avoid wasting render cycles. These are distinct issues from my view. What am I missing? Why shouldn't React "just" do useMemo by default?
> blindly applying React.memo should be a huge win
That’s what the react compiler does, and it’s a good idea in that case because the compiler knows how to do it correctly, for _everything_. When humans try to do it, they will likely get it wrong (see the real world example in the article, this is the norm imo).
I still don't 100% understand why `React.memo` is not applied by default, wrapping every single component in a `memo` is crazy overhead and non-trivial to enforce codebase-wide.
Sure performance is a concern but is recursive re-rendering really cheaper than memoing all props?
Someone used to argument with me that "it will make render pass that use external value works if it always renders".
But I wonder should it even work at first place? It feels like you are covering the problem instead of actually fix it. Make broken things broken is a lot healthier for the code base in my opinion.
Immediately find out things to be broken is 100x better than find out it suddenly not works and can't even figure out why is it broken months later in my opinion.
Use external value in Vue is very noticeable in contrary to React. Because it will definitely not work even once. The in component computed or component itself both enforce memorize . Render will be skipped completely it you used external values there so the framework did not notice anything is changed.
that's the the react compiler is supposed to be able do, memoize when necessary
Whenever I see articles like that, I can't help but feel the migration from Class components to functional components with hooks was a big complexity jump for little gain.
I've been toying with an idea of a pattern. I'm curious as to if it has a name. I'll write a blog post once I have an app using it. In the meantime, it's (roughly):
You end up with a diamond with all state on the top and the root of the dom tree on the bottom.One note, is that the tree is lazy as well as memoized, there's potentially many computations that don't actually need to be computed at all at any given time.
You need something like Rx to make an observable of the root state, some other tools to know when the external state changes. Some memoization library, and the react is left with just the dom diffing, but at that point you should swap out to a simpler alternative.
Have a look at Legend State, it lets you do something very close to this (leaves get their data out of the global state directly) with React.
https://legendapp.com/open-source/state/v3/
you can do something like this with most global state libraries, Jotai to name one. But very soon you'll see that you need effects there, so you'll need that global state solution to be rock solid in that aspect
I don't have a problem with needing to memoize props passed to child components for their memoization to work.
If your parent component doesn't need the optimization, you don't use it. If it does need it, your intention for using useMemo and useCallback us obvious. It doesn't make your code more confusing inherently.
The article paints it as this odd way of optimizing the component tree that creates an invisible link between the parent and child - but it's the way to prevent unnecessary renders, and for that reason I think it's pretty self-documenting. If I'm using useMemo and useCallback, it's because I am optimizing renders.
At worst it's unnecessary - which is the point of the article - but I suppose I don't care as much about having unnecessary calls to useMemo and useCallback and that's the crux of it. Even if it's not impacting my renders now, it could in the future, and I don't think it comes at much cost.
I don't think it's an egregious level of indirection either. You're moving your callbacks to the top of the same function where all of your state and props are already.
> At worst it's unnecessary
Not really. The problem goes beyond re-rendering 15 times. For instance, how do you instrument usage? It can’t be simply debounced.
Similarly, you’ll be making unnecessary requests. e.g., we need to fetch X when this prop changes.
Or, we need to lazy load X once.
And back to re-rendering, there’s plenty of apps rendering @1FPS when dragging elements.
Thank you, that was the example I needed to hear to see why this could be an issue.
I will still say though, I have not actually had this happen to me yet with all the years of using hooks. Generally when I'm fetching when X prop changes, it's not in response to functions or objects, and I guess if it's ever happened it's been fixed and never broke or hasn't caused problems.
Not to say it isn't an issue - it is - but the number and degree of issues I saw with lifecycle functions was much worse. That was with a less experienced team, so it could just be bias.
The useRef pattern seem like a code smell, maybe because I had some troubles with refs over the years.
Things were simpler when we had lifecycle methods to manage some of this things. But I`m sure that the next version of react will change everything and make us come up with more patters to try to fix the same problem again...
useRef is quite useful when you want to use something externally that has it own lifecycle. And your only interaction is sporadic. Think animation.
What do you mean you don't need 8 dependencies for one useEffect? How else will you update this string!
useMemo and useCallback - LLMs do love including them! It drives me crazy removing them
Why not tell them not to use them?
i do, rules and over and over...
don’t even get me started on react strict mode. I’m looking at porting a couple of my personal apps to rails 8
React needs a useful useLessCallback optimization, with an "off" switch and a hand for turning itself off, right out of the box.
Useless Machine:
https://en.wikipedia.org/wiki/Useless_machine
A useless machine or useless box is a device whose only function is to turn itself off. The best-known useless machines are those inspired by Marvin Minsky's design, in which the device's sole function is to switch itself off by operating its own "off" switch. Such machines were popularized commercially in the 1960s, sold as an amusing engineering hack, or as a joke.
More elaborate devices and some novelty toys, which have an obvious entertainment function, have been based on these simple useless machines.
History
The Italian artist Bruno Munari began building "useless machines" (macchine inutili) in the 1930s. He was a "third generation" Futurist and did not share the first generation's boundless enthusiasm for technology but sought to counter the threats of a world under machine rule by building machines that were artistic and unproductive.
A wooden "useless box"
The version of the useless machine that became famous in information theory (basically a box with a simple switch which, when turned "on", causes a hand or lever to appear from inside the box that switches the machine "off" before disappearing inside the box again) appears to have been invented by MIT professor and artificial intelligence pioneer Marvin Minsky, while he was a graduate student at Bell Labs in 1952. Minsky dubbed his invention the "ultimate machine", but this nomenclature did not catch on. The device has also been called the "Leave Me Alone Box".
Minsky's mentor at Bell Labs, information theory pioneer Claude Shannon (who later became an MIT professor himself), made his own versions of the machine. He kept one on his desk, where science fiction author Arthur C. Clarke saw it. Clarke later wrote, "There is something unspeakably sinister about a machine that does nothing—absolutely nothing—except switch itself off", and he was fascinated by the concept.
Minsky also invented a "gravity machine" that would ring a bell if the gravitational constant were to change, a theoretical possibility that is not expected to occur in the foreseeable future.
This reminds me of a coworker that was writing a program for some late 80's based DSP accelerator. He tried compiling the thing and nothing happened for a few minutes and then it printed out 'too many errors'.
When he went to edit his program it had been deleted. So he rewrote it and tried compiling it again and found the compiler had deleted itself too.
I like the idea of a program that takes increasingly dire steps to prevent you from compiling it each time you try.
I remember when React api was unique, then it comes up with some idea (hooks) and other front-end frameworks copied it, that's like confirmation that the idea is crap.