Hacker News new | past | comments | ask | show | jobs | submit login
You might not need an effect (react.dev)
242 points by lawrencechen on March 23, 2023 | hide | past | favorite | 240 comments



IMO if you need a long doc like this pointing out the sharp edges, then I think you've done a poor job in designing the framework.

I love the terseness, reusability, and typing of React hooks, but hooks have too many weird edge cases (this article, dependency list, order of hooks, etc) versus class components, whose design was simple and elegant.

I'm just an old man yelling at clouds though, the terseness of stateful components defined in a function, plus simple typing with TS (no defining proptypes) is too appealing to me personally. Maybe I'll check out Solid next time, which seems to have less weird edge cases.


I actually disagree here pretty heavily and think that this doc is due to the bar for competent developers going down in recent years (due to the rise in necessity for more developers of course). There's almost an expectation that powerful tooling should be dumbed down.

The entire doc is written around two common problems:

* You don’t need Effects to transform data for rendering * You don’t need Effects to handle user events

Both of these problems do not occur if you take the time to understand the tooling React gives you. The events point should be a given, that's just React 101 (and yet they have to spell it out to people...).

You don't end up using useEffect to transform props/state and generate new state if you understand that those things are just variables and you can instantiate new variables using them inside the scope of the component function.

"But that's inefficient because of how re-rendering works".

Great, you understand how re-rendering works but haven't gotten familiar with the useMemo hook. Come back when you've gotten familiar with the primitives available to you in React and how they can all work together in different ways, there's not that many of them!

This doc reads to me like a DIY store publishing an article about how you shouldn't hammer in screws even if it kinda seems like you could if you didn't take the time to understand hammers, screws, and nails. It's sad that it needs to exist but that's the current state of engineering.


Your entire argument here is a shining example of how the growing complexity of react has raised the bar of skill for competence rather than being a victim of lowering the bar.

Think back to the webdev world before react. Was it really that the average dev was more competent, or that the tools of the day were in fact more simple to use? Sure there are more and less efficient ways to use jquery, but unlike react you didn't have to keep so much in your head at once while building a simple feature.

React was designed to be a UI component library. We keeping asking more of it and stuffing more and more complexity into react so that monolithic frameworks can be built with it. It was never designed with that goal in mind and is taking more and more complex features to try to keep that ship afloat.


I really like React. I don't know if this particular pattern was explored before, at least I didn't see it, and I used plenty of UI libraries, from WinAPI, MFC to Java Swing and plenty of web frameworks.

I think that JSX support is a genius thing. Especially with modern TypeScript. I spent so many hours because of string-typed templates that I really value templates integrated within a language.

I really liked original React version with classes. It had its rough edges, but it was simple and accessible to me.

Modern React with functions is not simple. It's so far from simple. I consider myself pretty talented developer. But those hooks - I think last time I had to spend that amount of time is when I learned Scheme continuations. That thing really broke my mind. Well, hooks didn't break my mind, but they just seems to be very easy on the surface yet very hard below surface. I read plenty of articles yet I still don't fully understand them (although I think that at least I can use them without bugs).

For me React is like ORM. It's very powerful tool. I allows to tremendously cut the development time. But it requires absolutely expert knowledge on the team. And it looks so deceptively simple at the same time. This is a bad place for technology to be in.


I first tried react rigtt as it was shifting from class-based to functional components. I actually really liked the functional approach at first, but to me the promise was just pass in props and spit out html. No async components, no hooks, no data fetching, at best it just needs a way to attach even listeners once that fire custom events.

With all business logic outside the render tree it makes a ton of sense and really can be understood extremely fast. Props in, events out. Done. Today everything is a component, and components even have to understand whether they are in the server or browser and how to manage that handoff. It's such a massive dumpster fire of convention-as-a-solution.


Having built a SPA without React it was 100x harder to do it. You had to be careful to update so much state that there was no way really to not break far away stuff when making small local changes. It was a maintenance nightmare.


Yup. And jQuery, awesome as it was, wasn't designed to address the state issue. But it was the best DOM abstraction, so that was the backbone (no pun intended) for many pre-framework applications. It was a spaghetti nightmare.


> We keeping asking more of it and stuffing more and more complexity into react so that monolithic frameworks can be built with it. It was never designed with that goal in mind and is taking more and more complex features to try to keep that ship afloat.

The trouble with a smash-hit library (or, let's be real at this point: framework) is that adding more whizz-bang features to it becomes irresistible, from a résumé-building perspective. Add in that a bunch of the devs are paid very good money to work on it, and they really do have a lot of incentives to keep finding more shit to add to it, rather than calling it "done" and letting it enter maintenance mode.


> and think that this doc is due to the bar for competent developers going down in recent years

"The tool isn't poorly designed, everybody is just too stupid to understand it."


Perhaps more like, "the tool is designed with the lowest common denominator in mind, but the denominator keeps getting lower and lower".


> It's sad that it needs to exist but that's the current state of engineering.

Wait, wait. I'm trying to make friends with React for less than a month and I do appreciate this article giving me some understanding about the primitives react gives me and when to use it (Or how you called it screws/nails)

I did understand more, but I'm left with a feeling I cannot follow the "flow" of the useEffect as I couldn't understand how can items !== prevItems EVER be true?

  // Better: Adjust the state while rendering
  const [prevItems, setPrevItems] = useState(items);
  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null);
  }
useState doesn't return immediatelly? Or does it cause immediate re-render? I kind of gathered from the article that all useState calls cause re-render whenever it encounters return.


I am not sure I understand your confusion but here's a few pointers in no particular order:

- the argument passed to useState is initial state, not "forever state". It will seed prevItems for the first render. But later when items prop changes, it's on you to detect the change to what you have as prevItems so far and call setPrevItems yourself.

- items is coming as a prop from outside, prevItems is whatever you set it to last time using setPrevItems

- calling a state setter (setPrevItems / setSelection etc) will always cause a rerender. That's why if you don't have the if condition you have just created an infinite loop - you are inside a render, and if you unconditionally call setX that causes another rerender later etc etc

- that's why I am not a big fan of this particular "Better" example and prefer the later "Best". Avoiding calling state setter function from inside render will make your life easier.


Well, in the first place, this code snippet has nothing to do with useEffect, so I'm not sure why you're bringing it up. They have a separate page that they linked to that explains this pattern in more detail: https://react.dev/reference/react/useState#storing-informati...

But to summarize that page briefly, useState will always return the current value of the state and a function to set it. But "items" here is being used as the default value that's provided the very first time the component is rendered, when the state doesn't exist yet. In subsequent renders, the state exists, so "prevItems" will reflect the last value that was set with `setPrevItem`, not the current value of `items`


I did read the other page and google around... but you and the other comment nailed it for me, thanks.


yeah I agree with a most of this, but useEffect is still a shit show. It's nasty, and it sits in your code like an anvil, heavy and terse, it even looks like one, with it's lopsided dependency array. The semantics are so strange, and it's not even really for side-effects, more so for synchronizing state. And while I love React, it's not uncommon to see entire codebases where every function is wrapped in useCallback and useMemo, you know, "just in case".


I had a horrible time trying to convince junior devs that they didn't need to memoize everything. Ended up just quitting rather than work with that shit show of a codebase.


> Both of these problems do not occur if you take the time to understand the tooling React gives you.

Wait, there’s people in this industry who are taking the time to understand things before using them?


Overuse of useEffect is my number one pet peeve for juniors. I can't tell you how many times I have seen a total mess of manual data passing between 3 components with so many if..else cases to handle all the weird stuff.

I catch it in code review and show them that when you delete all of it without useEffects and useStates that it just works. Its an antipattern that turns the code into a giant ticking time bomb if you don't manage all of it out.


> I actually disagree here pretty heavily and think that this doc is due to the bar for competent developers going down in recent years (due to the rise in necessity for more developers of course).

This was a non-issue with class components.


> There's almost an expectation that powerful tooling should be dumbed down.

Which is the problem here. The abstraction in question tries to dumb things down, but doesn't succeed in dumbing things down enough. As a consequence you get their weird middle ground where you can't access the gritty details an experienced programmer would want, but it doesn't hide the gritty details from the inexperienced programmer, ultimately serving no one. As usual, you can make it work, but it is a poor abstraction.


I have no idea what you just said :/

But I barely know React.


No you're right, I completely agree with you. It's also very telling that there's also this very long article about useEffect dependencies: https://react.dev/learn/removing-effect-dependencies

useEffect and dependencies are the worst part of React at the moment.

The reason is, you do need to use effects, everywhere in your code, constantly. And dependencies, and the linter rule that goes with them, are nasty ergonomics, and hard to reason about.

The article I linked does explain it all quite well, and most devs I work with do understand the dependencies rules, but the rule still needs to be disabled often. Why?

Often the lint errors really are just wrong. Just like ChatGPT is often simply just wrong, no ifs or buts. For example: you have a callback function declared as an expression, that the useEffect hook calls. The linter will say "oh this is an expression, therefore it needs to be declared as a dependency". But it doesn't, because looking at the code as a human, you can immediately see the function never changes.

If you follow the linter, you will inevitably end up going down a destructive and unnecessary refactoring. You'll end up with unnecessary `useCallbacks` sprinkled everywhere that hurt readability for nothing. Often, you'll have to refactor the dependencies out of the component entirely, splitting one cohesive component into two, just to satisfy the linter out of an abundance of caution.

Junior devs will be confused. Senior devs will be frustrated. Every react codebase I've worked on has gagged this linter warning.

So how should this work?

It shouldn't be a a linter error in the first place. Linters should be used to enforce coding standards, not fix application bugs. I feel very strongly on this.


"For example: you have a callback function declared as an expression, that the useEffect hook calls. The linter will say "oh this is an expression, therefore it needs to be declared as a dependency". But it doesn't, because looking at the code as a human, you can immediately see the function never changes."

Can you elaborate on this? In my experience this isn't the case. If a function is declared outside of the component scope then it won't trigger a lint error. If it's defined within the component scope and not wrapped in useCallback then it does in fact change on every render and the lint error is being raised for good reason.


> If it's defined within the component scope and not wrapped in useCallback then it does in fact change on every render and the lint error is being raised for good reason.

So I just did some playing around to investigate this further, and it seems to be more subtle than I thought.

If you have a function expression but the expression is not dependent on any outer scoped variables, the linter does not complain that you didn't specify the function itself as a dependency in useEffect. Example:

```

  let bar = "bar";

  const setResult = (result: string) => {
    // Won't trigger "exhaustive-deps"
    setBio(result);
    // Will trigger "exhaustive-deps"
    if (bar === "1") setBio(result);
  };

  useEffect(() => {
    setResult("");
  }, [person]);
```

So there's some quite sophisticated analysis of the AST going on in the lint rule.

I still think that this rule is too complicated for a linter. (Bugs like inter-dependencies in your data and code should be caught by unit tests).

But it's technically pretty impressive.

Edit: I did some more investigations.

If I make a function that depends on something very unpure (like checking window.location), the linter fires if the unpure function is in the outer scope of the hook, but not if it isn't. Even though the dependency is the same. So the linter rule can only identify locally scoped dependencies I think.


I recommend you read the article again. Just because window.location changes outside scope doesn't mean that React would update the component when window.location changes just because you put it in a dependency array.

This is because props or state didn't change and the parent didn't rerender.

UseEffect dependencies aren't observables. You'd need to wire up an event listener on unload, hashChange, etc

https://stackoverflow.com/questions/58442168/why-useeffect-d...


> Bugs like inter-dependencies in your data and code should be caught by unit tests

Why write a ton of unit tests for what a static check can just do “for free?”. Not sure i follow your logic. I agree React can feel like a nuisance, but the lint rule itself is not the issue i think you have, it sounds like your issue is just with hooks.


Its not necessarily that the function depends on something 'very unpure', its if it depends on anything which is non-primitive that is created or assigned within the component fn body.

window.location returns a Location object which is not a primitive, and so it will trigger exhaustive deps if it is being used by way of a reference inside the component render function (assigned to a variable), since it could theoretically be a different object reference on subsequent renders.

Since the linter can't necessarily tell if the reference has changed out from under it on subsequent renders due to the fact that it could be mutably changed, it marks it as a necessary dep.


This seems obvious to me? Your lambda captures an outside object that will change with each render pass?


There is a new experimental hook, useEffectEvent, whose only discernable functionality based on the documentation and information available is to quiet the linter. I'm not sure that's all it does since I haven't dug into the React source code, but that's what many are speculating.


[Edit: this is all wrong, see comment tree]

It makes it so that the same instance of a function is used for all renders, but that it always points to the data closed over by the latest render; it eliminates a whole class of scenarios that normally lead to gnarly dependency array tomfoolery with useCallback, useMemo and useEffect.

It's quite a common hook to try to invent in user-land (i've seen it in numerous projects), it's not just a linter silencer.


> but that it always points to the data closed over by the latest render

It's my understanding that this was to be the behavior of the canceled useEvent hooks but that the new useEffectEvent hook does not return a stable reference.

Do you have sources for the behavior of the new hook?


https://react.dev/learn/separating-events-from-effects#decla...

> Here, onConnected is called an Effect Event. It’s a part of your Effect logic, but it behaves a lot more like an event handler. The logic inside it is not reactive, and it always “sees” the latest values of your props and state.


A bare closure would always see the latest values because it closes over them during render. And the latest useEffect closure will have closed over that method..

Here is the test suite for useEffectEvent and the test ensuring it does not provide a stable reference: https://github.com/facebook/react/blob/main/packages/react-r...


You're right, i'll redact/edit. Hm, this doc page is exceptionally confusing.

Edit:

So it's kind of interesting, this test (and the functionality behind it) exists specifically to make sure you can't rely on it having a stable identity -- and therefore have to omit it from the dependency array. But it otherwise behaves the same as the popular user-land implementation involving refs (which you still have to include in the dependency array, because it works by providing a stable identity).

So there's some internal trickery in React to make sure that even though the effect event is recreated and has a new identity on every render, any time it's invoked you're accessing the latest inner callback.


As far as I can tell, useEffectEvent is just the useEvent hook but renamed to reduce scope. useEvent was originally going to provide performance optimizations when creating event handlers in a function component and it was going to solve the issue of event handlers causing effects to re-fire[1].

Now it's just going to focus on the effect issue.

[1] https://github.com/reactjs/rfcs/blob/useevent/text/0000-usee...


The funny thing is that React already kind of has its own programming language: JSX. If they wanted to, they could add language features to support React. I expect that would be very controversial though.


Svelte is pretty pleasant, compared to React. It's fundamentally a JS-to-JS compiler that adds reactive assignments, and ridiculously much more usable than useEffectHopeThisWasTheRightThing.

    let count = 1;
    
    // this is reactive
    $: doubled = count * 2;
    
    function handleClick() {
      count += 1;
    }


Reading sentences such as 'Effects are an escape hatch from the React paradigm.' in the official documentation in 2023 while writing React since 2015 makes one depressed and angry (deprangry?): then how are you supposed to use this 'library'. Luckily they provide a firstName/lastName example, because that's all we use React for, no?, login forms and hello world todos apps.

As with everything, if you want to make an apple pie, make a universe: after all, the mistake was mine, starting to use React in 2015 instead of making my own language/framework, mainly because what I have today is already a bunch of highly idiosyncratic React utilities. If there is one hope with the large language models hype is to finally be able to code against a true common interface (the HTML/CSS/JS specification), and do it anyway you please, translating on the fly any third-party dependency.


Part of the problem is that React was never a "framework" to begin with, but people treat it as if it is. React began its life as a view library for creating custom components. That initial design decision has permeated every attempt to make it more "framework-like."

In a traditional MVC-like paradigm, components would just be views. Discussions would be focused around keeping your components limited to view concerns while using other, non-view parts of the framework to drive things like API integration, business logic, state management, routing config and so on. Which is to say that you wouldn't have a need for "effects" at the component level in the first place since, by the time your data is ready for presentation and your components are initialized, all of that has already been done separately.

So yes, it is a design flaw with the "framework". One that came about by trying to morph what was, initially, a simple view library into something more "framework-like."


IMO the simple old class & props was all that was needed. Lifecycle was much easier to understand, most reusability issues could be solved one way or another.

Most bugs I find in big react projects are either by using multiple useEffects in one component, and the other one with Saga. Also another "side effect" tool.


Class component usage patterns where "lifecycle was much easier to understand" were inherently problematic though; they led to redundancy and verbosity, grouping unrelated code in a given lifecycle method and fragmenting otherwise-related code across disparate methods. Those deprecated lifecycle methods are overly imperative, with poor ergonomics. (Dan Abramov wrote a great post about these shortcomings; I'm on my phone or I'd try harder to dig up the bookmark).

Don't get me wrong, I have fond memories of using classes as bags of methods (esp with Mobx) and it took me a while to come around on hooks. But given your "most bugs... useEffect... and Saga" (does anyone still use Saga?), maybe the over/misuse of useEffect -- not hooks / FC more generally -- is the crux of the problems you've encountered. Which might even reinforce the OP (shrug). :)


Those are tradeoffs. Lifecycle methods had pros and cons. Hooks have other pros and cons. Lifecycle methods had the right pros and cons for most devs. Hooks are horrible for new devs, as they have to many gotchas, and there far too many people that just don't have the time to learn all the footguns of hooks.

So lifecycle methods had some other drawback? Yes, sure, but they were less than the drawbacks of hooks for most people.


We're agreed that nearly everything has pros and cons.

But these confident assertions about what's "right" or "horrible" are subjective, personal preferences.

And unsubstantiated claims that they apply to "most devs" or "most people" is merely a projection. That contradicts my direct experience (I've never encountered a single team that adopted hooks and went back). Hooks' adoption rate (along w React's market share) suggest I'm not an outlier.

All that said, I'm glad TMTOWTDI. I also want to pause and thank you for sharing your perspective; dissent is valuable and helps prevent groupthink.

Have a great day!


It's hard to leave hooks when you are using React, as they are everywhere. What people do instead is use something else next time they start a project.

Just look at the exponential growth of Solid: https://npmtrends.com/solid-js

This will happen fast. I'm not even using Solid - I like the html first approach of Svelte - but the writing is on the wall. The data is saying as much.

I was using Angular in 2013 when React came around. I knew Angular was dead as soon as I saw React. Took the Angular people many years to realize this, and many still haven't. I'm saying React is already dead. Svelte and Solid killed it. It will take a few years to play out, but you can see it happening already if you pay attention. The React devs are doubling down on the inherit complexity of React instead of cleaning it up - this is exactly what killed Angular and made people move on to something simpler.

I am extremely confident that this comment will age well, but we'll see :D


> Just look at the exponential growth of Solid

How can you be so sure that growth isn't just riding a larger wave of web dev growth in general?

Remix react and has a steeper curve: https://npmtrends.com/@remix-run/react-vs-solid-js

Nextjs is still growing fast.

What's the stats of Solid apps in production vs people just doing tutorials?


The steep curve of Remix is really beautiful. One possible way of interpreting it is that it is coasting of React, and isn't starting from as low a starting point as Svelte and Solid have to. Just the mere fact that Solid is showing a hockey stick curve, and that it is so different from React is what has me convinced.


Sure; Svelte and Solid look promising. And for those of us who like TSX and the better parts of React, Remix's embrace of web fundamentals and its relative simplicity are a breath of fresh air. It's powerful and intuitive, with a really well-designed set of features that represent a coherent paradigm. The return to web fundamentals -- eg its approach to forms -- resonates deeply with me and my peers (building websites and apps for a living since 1998). A rising tide lifts all boats, and I'm glad there are choices.


It was pretty easy to group unrelated code into a seperate function either in the class or in a seperate module. Every beginner programmer can do that. To write a hook is much more complicated.

Dan is very smart, also redux was very smart but ended being an overkill. Side effects are a concept only thought at the Uni.

That's the core of the issue, useEffect is too complicated. As well as Saga. Even for simple requests it can become tricky to figure out what the flow is.


I used to frequently see people misuse componentDidMount/componentDidUpdate in every single way that is listed here.

There's really not that much difference.


Not all sharp edges are equal, the sharpest (according to % of traffic to my blog, anyway) seems to be race conditions (https://maxrozen.com/race-conditions-fetching-data-react-wit...), and that's not even unique to useEffect...


I've run into race conditions inside of react that has made me gobsmacked. I swear the thing must be written by fresh faced middle schoolers.

I'm so glad I don't have to deal with it anymore. After being in programming for over 20 years, I started making plans to exit the entire industry. Now I realize it was just react. That thing made me despise programming. I kept a virulent twitter journal of my time in react. It seems foreign to me now. Good! Example(s): https://twitter.com/SisNode/status/1458942812087414797 ... it made me pretty intolerant https://mobile.twitter.com/SisNode/status/143380932394827366...

The tweets in that account are like an insult comic does programming.


From this account, you seem very unpleasant to work with. Anger just makes you more angry and bitter, you gain nothing by being this angry, and people who see your comments are hurt by them.

You said you keep it private and try to be well behaved, but it probably comes out in your interactions with others, whether you're aware of it or not.


I appreciate your concern, really. Thanks!

I used to do comedy when I was much younger and I've always had a love for it. I've tried the unhinged neckbeard character a few times. For instance, here's 11 years ago https://mobile.twitter.com/gitHater/status/16997810622706892...

Here's authentic me for comparison https://mobile.twitter.com/emoRobot


I think I just found a reason to use Twitter. This might be the best Twitter account I've ever seen lmao.


hey thanks. I know there's always many actual people behind all these projects trying their hardest. I hope nobody takes my jeers personally. I'm playing a character.

I mean sure, the passions are real but I'm also ostensibly a well behaved sincere adult. I don't actually want to "literally stab the people who wrote [apache modrewrite] repeatedly in the eyeballs" - although a reconsideration of how transparent the flow is would be greatly appreciated.

Perhaps I should add that to my list of "things to do" and you know, fix it myself. Be responsible or something. I've become intentionally unemployed in order to spend some time on stuff like this and regroup. I should do it.

Honestly, I'm probably still recovering from that react project. Maybe I should do some therapy. It was bad.


looks like we've unknowingly avoided a lot of issues by using redux and sagas.

this model clicks so perfectly I still wonder why it's not the standard.


> IMO if you need a long doc like this pointing out the sharp edges, then I think you've done a poor job in designing the framework

And it seems that the React maintainers don't understand their own system, in a sense.

That obviously sounds a bit weird, but as far as I can tell they seem to think that what makes React good is that it is "functional". And so when something isn't quite right, they "fix" it by making things more functional. And that seems to invariably make things worse. Rinse and repeat.

Hmmm...

What if the good part of React is that it is a reasonably decent UI widget framework for the web? And that the FP-ish nature is really more an implementation detail due to the way it is embedded into the web platform? And that this led to the happy accident of being able to write the UI as a procedure that structurally reflects the UI to be displayed?


Really it is quite an amazing situation. The conceptually clunky but easy to understand class components vs the conceptually pure but esoteric hooks. Even at the time the React dev team were putting in a lot of effort to say "you will get it soon, don't worry", because they already knew it was in many cases difficult to understand and use properly. That should have already been enough of a sign to not push it so hard.


Be warned that the state of docs is very weak, and that although the Solid team acknowledges this issue and are working on a beta¹ version of the docs, the effort really crawls along. It's also really hard to get help. Maybe there are many Solid lovers, but there aren't enough Solid intermediates to help prune the support forum. I attribute this to slow progress of the docs.

[1]: Note that this docs effort doesn't include SolidStart, which is a big bummer.


> I love the terseness, reusability, and typing of React hooks, but hooks have too many weird edge cases (this article, dependency list, order of hooks, etc) versus class components, whose design was simple and elegant.

You know, I kind of agree and personally prefer Vue (that does hooks better in some ways, as well as has a simple store solution called Pinia that's nice to use), but I have to say that these docs themselves are great.

Even if there is some awkwardness about using React and it needs some getting used to, to be able to use it effectively, I think these docs are definitely a step in the right direction and offer concrete examples! The code is mostly clear, the text explains everything bit by bit - we could use more of that in our industry for sure.


It's a sad state of things where React—even with hooks—is considered by some developers as "terse". It makes me wonder if we're all actually speaking the same language.


The problem is that React was designed to allow you to use functional programming to express a UI that is a function of state. It might be obvious to experienced functional programmers not to sync internal state using effects in this way, in no small part because they understand what a side effect is already.

> dependency list

This, again, is only confusing if you don't understand what an effect is for: syncing with systems external to React. How else would you say "do the thing when any of these change"?

> order of hooks

I never understood why this was confusing for people. It takes 2 seconds to learn this rule, and your browser console will yell at you if you violate it.


There is nothing in React I'd regard as "terse". Perhaps slightly more terse than the mountain of boilerplate it required previously, but that's as far as I'd go.

https://youtu.be/AdNJ3fydeao?t=702

We really should accept that all of the following are ABSTRACTION LEAKS and not inherent benefits to web development at large:

  • shouldComponentUpdate
  • React.PureComponent
  • useMemo
  • useCallback
  • Concurrent mode
They exist solely because React's underlying model is insufficient and we, the developers, are being made to do the computer's job with all this optimization boilerplate. The VDOM is dying. Let it die. It's long past time to move on. Any further React development in the existing foundation is just continuing to build upon sand.


Completely agree. Class components are so beautifully simple. Hooks have infuriating edge cases and are so bizarre they need ESLint plugins to remind people how they work.


There's a running in circle feel about react right now for me.

It's the same old OO (state+methods) vs FP (lexical scope + closures) thing, in the dom now.

A lot of react state libs are also tweaking around this idea.

Not long ago a jotai class made me see this.

    <Component />
    <Component />
these components use the same state defined in the source

so to avoid that

    <Provider>
        <Component />
    <Provider>
    <Component />
which now intercepts the state object creation to have two independent states. I have a feeling that soon we'll see

    <new class={Component} />
strange


Solid has very similar API, but much simpler concept (and of course performance), it's just a plain js signal library with some ui helper functions no dark magic, whereas react is the most framework framework out there.


useEffect was designed to allow shooting yourself in the foot, in a good way. There's never a situation when react doesnt allow you to accomplish something - the page even a mentions of synchronising with jquery!

Whats the alternative you propose? Not allowing react to be used with any 3rd party libraries or access to browser apis?


>useEffect was designed to allow shooting yourself in the foot, in a good way.

Ok... no... there is no good way to shoot yourself in the foot (metaphorically or real)

>There's never a situation when react doesnt allow you to do something

That is EXACTLY what I DONT want from my framework:


>That is EXACTLY what I DONT want from my framework:

Unless you're happy about never going out of the guardrails set by your framework, there's going to come a point where, one way or another, you're going to need to interact with something outside of it. Are you going to wait for an official React-WASM way to interact with WASM (which is going to be using useEffect under the hood anyways)?

Frameworks with no escape hatches are hell. The escape hatch just needs to be bright, bold and with a warning on it that says "there can be demons behind that hatch, watch out".


So, that's accurate, but doesn't correspond to the situation. At all.

'Escape hatches' are definitionally exceptions to the rules, whereas effects in React are standard practice.


Because React evolves in an ecosystem which is wide and varied. It's easy to be Elm and have no escape hatches when you make the language, the libraries, the standard library and the world. It's a bit harder when you have to interact with 30 years of crap.


[allow shooting yourself in the foot] in a good way


Sorry for the disgruntled snark in advance:

Heh, we already have that and it is called Elm.

By me, an Elm user until 0.18.


Yeah, it's a right pain to deal with non-Elm things in Elm (including JSON data sources). It's possible but you will want to avoid it. However, I think I'd still take Elm over React if I didn't have to care about anyone else but myself.


Yes


> IMO if you need a long doc like this pointing out the sharp edges, then I think you've done a poor job in designing the framework.

Thank you. It's self-serving (mea culpa), but this is the exact reason I moved away from React and decided to build Joystick [1].

[1] https://github.com/cheatcode/joystick


I've used Surplus for years and love it. Highly underrated framework. It's hooks, but designed properly, and consistently beats out almost all other frameworks performance-wise.

It's in need of a few upgrades though, namely a few edge cases in the old JSX parser (it was written before Acorn had good JSX support) but works pretty much perfectly still today.


> It's hooks, but designed properly, and consistently beats out almost all other frameworks performance-wise.

Performance is nowadays one of my last concerns with frameworks. They're all snappy enough except for corner cases. More important is whether there are standard ways of doing things vs everyone inventing their own. Our company uses Vue which has standard lifecycle methods, standard way of scoping CSS etc, officially endordsed store etc. Makes it much easier to bring people up to date when they're joining the projects.


One thing that I don't understand: why design useEffect api with dependencies array which does not get passed in the function?

If you ask me, I'd design it the following way:

  useEffect(f, {a, b, c});
  
  ...
  function f({a, b, c}) {
    ...
  }
Function f is supposed to be declared outside of render function.

This way you must specify all the dependencies. JavaScript will bite you with undefined value, TypeScript will bite you with compilation error. The drawback is that you'd need to pass setValue functions which are typically immutable, but I don't think that's a bad thing anyway.


> "Function f is supposed to be declared outside of render function."

That's the cinch. To get the benefit of this proposed API design, the effect handler functions must be be outside of the render function so they can't capture any values other than the explicit dependencies. That's not a requirement you can enforce in JavaScript, so it would have to be a linter-level thing.

I think it would aesthetically run counter to the prevailing design of React functional components. For better and worse, it feels like the framework designers have made a substantial effort to contain a component inside a single function. With this design, you'd have the component and its effect handlers in the same parent scope even though the handlers are subordinate to the component.


How would useEffect () access those dependencies? useEffect() them to determine if f() needs to run again or not.


`{a, b, c}` is a syntax sugar for `{"a": a, "b": b, "c": c}`, i.e. useEffect has access to the dependency values.


> `{a, b, c}` is a syntax sugar for `{"a": a, "b": b, "c": c}`

Not in function arguments. In function arguments that syntax does destructuring.


I don’t see why productive frameworks must be obvious. Once you learn how to think in functional react, it’s a very productive framework that enables you to bang out applications


>IMO if you need a long doc like this pointing out the sharp edges, then I think you've done a poor job in designing the framework.

x1000 upvotes.

One alternative is of course svelte, the unofficial-slogan (svelte) is/was that you can explain it to someone or learn it yourself in an afternoon.

Reminds me a lot of C++ vs Go in terms of Public/Private/XYZ

Go: To make a something public, start the var name with uppercase.

C++: To make things public read the Chapter on "Access Modifiers".


While I agree with your point, the example you gave feels like a straw man. Or at least, a separation of "sharp edges" and "long documentation needed".

Phrased differently:

Go: To make a something public, start the var name with uppercase.

C++: To make things public, type the word 'public'.

The Go pattern seems like more of a sharp edge than the C++ version, even though the C++ version might need more reading to learn.


Go using casing for access is quick but is awful when you just want to change the access later on. It's not like writing `pub` before the var/field is suddenly javaland, see rust.


What do you mean changing the access?

You mean from private to public?

In that case, you would just have to change a single package.

(also, if yes: does it happen often and is that really what we should optimize for then?)


Yes, you have to rename a var/field to change public/private. It's not a huge deal of course, just an annoying exception.

Re: optimization, what does using casing instead of an explicit `pub` prefix optimize for, exactly? Compiler complexity? Typing ergonomics?


I think it optimizes for readability.


You ever read the access modifier rules of C++ ?

Can you explain that to a newbie in 2 sentences ?


No, but I don't think that's relevant. Rust or Java access is doesn't require much typing ceremony, and doesn't require you to rename a var to change the access.


I got up and running in Svelte in literally 5 minutes, and it blew me away. I just started building and referencing the docs along the way. But then...I ran into their reactive model, and spent an entire day trying to debug (what I thought was) a very simple problem. I can't recall the details but I remember it had something to do with the order of updates? It was super confusing. Moral of the story is that there are always trade-offs.


> Effects are an escape hatch from the React paradigm

Hooks are overused in general. But I really disagree with this sentiment. Its useEffect and useLayoutEffect(okay useInsertionEffect too) hooks are THE mechanism they CHOSE to expose component life cycle. It's not an escape from React paradigm, it IS the paradigm. Function component React would be useless without these hooks; the DOM is an external system!

I'm really just put off by the React documentation in general. It feels like it's aimed at junior devs working on FE assembly lines and they want to keep the blinders on them. "Pay no attention to the man behind the curtain!".


Calling things an "escape hatch" (not used in normal operation) when they're more like an airlock (used during normal operation to access a dangerous area) is an irritating language choice.


Beautifully put, they really are more of an airlock


Counterpoint: I'm not a junior dev in general, but a couple of years ago I did my first project involving React. I came back to it a year later and was able to refactor away much hook usage and make components pure. It greatly simplified the codebase and I wish the documentation would have been more insistent on this back when I used it, so I didn't have to earn the experience on my own.

I guess the redeeming point is that hook usage is usually easy to refactor out, unless it's permeated your architecture somehow.


I agree with everything your saying I just disagree with the communication; well and I believe the line I quoted to be utter bollocks.

Dealt with projects that had evolved(devolved?) into hook monstrosities. 20-30 hooks in god components each with n-layers of "custom" hooks each permeated with useEffects and useStates chaining off each other causing a rats nest of render loops and maddening re-render puzzles.. It's awful.

I'm anti-hook in general and I use them very sparingly. But they, the effect hooks, are integral to React and need to be used to build the vast majority of anything interesting. People will need to use them or use something written by somebody else that used them. To me that's not an "escape hatch" but fundamental to the libraries usefulness.


I think we agree on all points, then. It's difficult to communicate this to beginners in a way that paints an accurate picture. Laud it too highly and it gets overused, downplay it and you fail to communicate a core strength.


You know what they say about blaming tools…


> I guess the redeeming point is that hook usage is usually easy to refactor out

Yes, but now imagine that you could've done it more or less correct in the first try. Refactors happen, but needing the refactor after it took 2 years for best practices to sink in? I don't think it's an endorsment for the hooks.

I refactored my old React app from classes to hooks when hooks became fashionable. Few years later I'm still sure that it's a mess and I've used hooks wrong. Am I itching to refactor again? No way. I let it rot some more, perhaps React will take another sharp turn and I'll save myself time doing it twice.


> hooks are THE mechanism they CHOSE to expose component life cycle

Yes, and component lifecycle itself is an escape-hatch (or airlock, or whatever).

> Function component React would be useless without these hooks; the DOM is an external system!

It is an external system, but most fundamental idea of React is that you don't have to write your own effects or lifecycle logic for updating the DOM (the great majority of the time). That's the whole point. The way React lets you update the DOM without using a side-effects programming model most of the time is the reason people use it. Instead of writing side-effects toward the DOM, you write a unidirectional flow of values derived from other values which bottoms-out in DOM (which React then "affects" for you). Custom effects are (mostly) for the situations where you're updating an external system that isn't the DOM.

The biggest problems in my experience come when effects become a part of the core domain logic. That's where things get really hard to follow, and that's the most important thing to discourage. Effects should be reserved for the boundary between the app and the outside world (minus normal DOM updates).


I agree completely. And that's what I liked about this article: it taught me what effects aren't for. I had been using most of the anti patterns in the article (especially using effects to sync props and state with each other), and it showed me a better way to do all these things.


The wording is unfortunate as I said in another discussion.

useEffect is not an escape hatch, it’s a fundamental building block.

The point of articles like these is that people use useEffect when they don’t need them. So it’s robust advice in that regard!

But you’re always going to use it, directly or indirectly.

For example if you’re doing anything interesting with your layout that CSS can’t exprees, which is fairly often, you’re going to need useEffect in order to read the DOM and calculate your layout.


> I'm really just put off by the React documentation in general. It feels like it's aimed at junior devs working on FE assembly lines and they want to keep the blinders on them. "Pay no attention to the man behind the curtain!".

Is this a problem specific to the documentation, or does it apply to React in general (or at least functional components)? Because I feel like it's the latter.


Or maybe you shouldn’t be thinking in terms of a “component lifecycle”.


Exactly - OP is perpetuating bad explanations.

https://epicreact.dev/myths-about-useeffect/#:~:text=Here%27...


That's bullshit; useEffect is a lifecycle hook.

From the documentation for the useEffect SETUP parameter:

> setup: The function with your Effect’s logic. Your setup function may also optionally return a cleanup function. When your component is first added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. After your component is removed from the DOM, React will run your cleanup function one last time.


It absolutely is. But the article and the comments above mention that it’s more useful to have the mental model of synchronizing side effects with state.

It’s good advice, because it addresses a major stumbling block people struggle with. After that the code and the way useEffect is used becomes simpler.


From the linked article in this thread:

> Here's the crux of the issue: useEffect is not a lifecycle hook.

That doesn't leave much room for ambiguity; seems like a definitive statement. Many don't see any nuance here and then go around blasting people for saying useEffect is a lifecycle hook because they read on some blog the exact opposite.

But anyway, what is a lifecycle hook if not a way to synchronize two systems... ( ͡° ͜ʖ ͡°)


The amount of groupthink from React influencers is a big reason why I don’t interact with the community.

Posts like this are almost always insufferable.


It’s not groupthink, it’s how the library was designed. What’s wrong with understanding the tools you’re using?


Lifecycle methods are a valid use case for hooks, yet that use case is constantly bashed (E.g. in this post).

There is a very specific way of doing React that influencers and sometimes the React core team push very, very strongly. Sometimes it's justified, but usually I find that it's personal preference being pushed as fact.


How do you handle route changes and data fetching, then? Back to Redux?


Those are results from a change in state, nothing to do with whether a component has appeared.


You either have some world state, like redux, or you have react components with hooks. With hooks, those have everything to do with the component's lifecycle. Hooks are included with react, world state management is not.


What? You still use hooks with Redux. And you could consider React's Context API as world state management if you really wanted to.

And these still aren't intrinsically tied to a component lifecycle.


Sorry I meant useEffect.

See react's official docs https://react.dev/reference/react/useEffect#what-are-good-al...

They recommend using react-query (a cached fetching library) and react-router. This combination definitely uses lifecycles to fetch data in the render functions.


Except I don't think they're using or thinking in terms of "lifecycles".

With react-query for example, when you use useQuery it communicates behind the scenes via Context with the react-query singleton. It doesn't fetch data because a component has rendered, it does so because a useQuery function call has asked for data it doesn't have in its cache.

That may result from a component rendering for the first time, or because that component's props changed, or because some piece of state somewhere in the app changed, etc etc. In the end it doesn't matter. It's not about a component's "lifecycle", it's responding to a change in lowercase-s state.


It absolutely does fetch because it has rendered. The route changes (or user clicks, whatever), a new component renders and it fetches the first time it renders. This is not a technicality, which is why the thing is called useEffect.

It does not fetch without rendering, once.


Except your first useQuery call happens before the component it’s in even returns any JSX for React to render.

And you don’t need a useEffect to do this sort of thing. You just need a function that creates a promise keyed to whatever identifier you passed in and some state to hold its status and result.


After the component gets mounted, in render code.

Yes you don't need useEffect. But the alternative is something like redux, where you're components are not (self-contained) components anymore. And the docs recommend effectful libraries.


A component can't render before it's generated markup to render.

And no, you don't need useEffect or Redux. You need a promise and some state to track it. Things like react-query aren't "effectful".


> A component can't render before it's generated markup to render.

So nothing ever renders.

> And no, you don't need useEffect or Redux. You need a promise and some state to track it. Things like react-query aren't "effectful".

Of course a Promise is a side effect. You're talking bullshit.


Things render after they've generated what they want to render, not before. If you want to render the result of 2 + 2, you have to run that calculation before you can render the result. When you call useQuery, you immediately and synchronously get the status of that query so that you can use it when generating the JSX you're returning to render.

And a side effect and useEffect are not at all the same thing. You're conflating generic software terms with APIs that have very specific meanings and functions within React.


And if instead of calculating 2+2 you call into some world state (or return a deferred call along with your JSX, doesn't matter), then that is a side effect. Period. The main effect is returning JSX (html and some event->callback descriptors), which could be pure.

useEffect is a side effect in a render function. Not all side effects are useEffect in a render function, good job.


Not sure how this got so absurdly reductionist. The fact remains, you do not need to use useEffect to fetch data in React. And useEffect runs after rendering, so you'd be delaying the fetch for no reason.


Never left


Same boat. It's tiring watching other React devs tie themselves in knots over component lifecycle (either via hooks or class lifecycle methods) when the actual solution is to use neither.


It's downright pleasant, with typescript. All the "waaaah boilerplate" stuff goes away. It's very simple (not even hard to reimplement, in a pinch). Truly does work well with non-React codebases, so if you build your business logic or at least your client-code around it, it's portable.

I stopped being a React fan with hooks, but remain a fan of Redux.


True, this is some really unfortunate wording. Since functional components don't have lifecycle methods anymore, relying on useEffects is pretty much the only way to subscribe to a prop change and render/handle its change. Now according to the new docs, advocating to go "local variables" way feels a bit non-React to me, actually.


What do you mean? When props change the component re-renders, doesn't it?


No, it doesn't, a component re-renders when its state changes or its parent re-renders. https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...


The article you link says that a component will rerender when its parent rerenders or state changes. It doesn't say anything about the props changing.

If you read this sentence "In normal rendering, React does not care whether "props changed" - it will render child components unconditionally just because the parent rendered!"

and understood that react doesn't re-render a component when its props change, well, I read that sentence in a different way.


Yes! It helps no one. I think that some authors are just infatuated with FP and can't get over the fact that only the most trivial widget is pure in React.


My experience has taught me that when developers consistently use your tools “wrong”, it’s a sign that the tools themselves are the problem. The use cases described in this document outline common needs that shouldn’t be so easy to get wrong. It reads like a call to action for an expansion and rethinking React’s public interface via new hooks or utility functions. You can’t always document the pain away. I’m not a React maintainer so I don’t know how easy or possible this is, but considering all the fantastic things the team has done, there’s no way that this is their best.

To paraphrase an old quote, “If you see bad code with your utility all day, perhaps your utility is the bad code.”


Joel on Software - The Pit of Success applies here, yet again


<aside>

Joel is a great but credit for this goes to Rico Mariani, originally quoted here https://web.archive.org/web/20100514093413/http://blogs.msdn...

</aside>


React would have needed its own domain-specific language to get around the implicit reference insanity and other quirks of JS.


I submit that React would have appropriately zero users if it nailed down its API to only allow the "right" way of using it from day 1.


disagree completely. the problem is newbie devs are learning react without know javascript first


Interesting Detail about this from Dan Abramov:

https://twitter.com/dan_abramov/status/1638773531881140224

> fun fact: You Might Not Need an Effect was inspired by a little investigation. i spot-checked random 128 useEffect calls in the Meta codebase, classified them into several groups, and 59 out 128 were unnecessary.

My own experience, on a much more limited scale, is similar. Especially developers new to React tend to write more useEffects than actually necessary. These often add unnecessary complexity and make the component much harder to understand.


I agree with this sentiment. I find that most developers seem to be stuck in thinking of their components as imperative. They expect to alter the state (dom) directly instead of describing the dom as a function of state (props).

Because of that, they quickly reach for the escape hatch that useEffect provides everytime they need to change something.

I genuinely don't know how to help my fellow team members grasp the basic concept of components better. It doesn't seem, to me, like it's something everyone gets eventually. I've actually seen far too many developers who just don't seem to get there even after long months working with component libraries.

As a side note: I don't get all the love for class components. All I remember from using them were the endless bugs because we somehow missed something on willUpdateProps or whatever. Class components could have worked but they always felt like the different life cycle methods were all cobbled together with little overall consistency (see Vue for an example of life cycle hooks done better, even if I have plenty of other gripes with Vue's design)


Indeed. React was a welcome innovation because it enabled V=f(S). That's it.

The vDom was fast enough that it made re-rendering your view on every state change possible.

Now you don't have to worry about rendering, you describe your components as pure functions and you're done with that side of things, now you "only" have to worry about the state of your app.

Seeing comments here wondering "How am I supposed to use jQuery with React otherwise?" is baffling and disheartening.


Sounds like this is more general pattern

I recently reviewed an architecture on AWS that has 5 services chained together to make information flow from source to target.

There was no justification for the middle three. The volume of data, loose coupling, native support for direct integration were all there to directly connect source to target.


59 useless useEffect out of? I think the meta codebase could have thousands and thousands of useEffect. That's a poor argument from Dan


He looked at 128 random useEffect calls in the codebase, and determined that roughly 50% of them (59 calls out of 128) were unnecessary.


I lead a 4 dev team working with a React codebase of 80k LoC for 2 years. Hooks' lack of orchestration mechanism, props drilling and its quirks, wrong dependency arrays, setTimeout not cleaned up, and other misuses of the framework were the cause of 7 of the 28 bugs we fixed in prod in March 2023 so far. Every other PR there is a debate around a hook lifecycle gotcha. Is it a lot? I think it is.


This is something that I really dislike about the react ecosystem. The devs and community leaders keep being impractically obtuse about how the framework should be used and is used.

React isn't low level enough to be this unopinionated.


This page should be mandatory reading for any dev working in react. The biggest mistake I see with devs of all levels is over use of effects.

This seems to be especially bad for anyone who originally worked with class components. The mental modal shift was large and people tried to fit their understanding of lifecycle methods into hooks, attempting to replicate similar behaviour. The docs at the time did a poor job of explaining why this was a bad idea.


100%. Effects are an escape-hatch, and it's really common for people not to treat them that way. All of the new docs are gold, but if I could only pick one to recommend that every React developer read, it would be this one


Second is abuse of useState for calculated values


A few months ago I need to build a small web app and decided to have a look at React. Pre Hooks, I had used React a lot so I thought it will be straightforward. Then I stumbled upon all these new paradigms wrapped in hooks to make an API call. Confused, I gave up and switched to classic Flask with template engines.

Seeing this reminds of the time I was spending to understand these concept and use them. Quite funny that the whole React ecosystem is turning into a classic server rendered mechanism with core functionalities being deprecated. I'm glad I stayed away.


Same here. Flask with HTMX and bit of HyperScript (if you dare) has become very pleasant so far. I wished I had discovered it before. I do not know for sure if it can fulfill complex apps, but this also invites to rethink an app in terms of the original web instead, exchange HTML, not JSON + all the transformations that need to take place in the FE. I found the HTMX memes hilarious and very true actually: https://htmx.org/essays/ Maybe is time to rediscover simpler tools and frameworks.


Just don't use hooks. Why people think they are obliged to do the switch eludes me, feels like some kind of cargo cult.


It's pretty clear that the class based API is a legacy thing in React by now. You can still use it, but the framework has moved on.


This is frankly framework's problem, not mine.


Anyone here jump ship to Vue3 w/ it's composition API? I feel like Vue has the best of both worlds by offering a functional paradigm (composition api) while still supporting the classical object-based options API. I am slowly transitioning object/options components over to the composition approach in a continuous improvement approach. The ability to do that at my own leisure has been quite nice.

I'm inclined to think Vue is the better framework, but React has a larger ecosystem.

(Not intending to start a flame war here)


Yes, we're using Vue 3 and composition-style components over at https://radiopaper.com. My only gripe is that Vue's standard templates can't be typed as thoroughly as JSX can (IME - after mixed experiences with Vetur/VTI[1]), but we've been gradually migrating everything to TSX via render functions[2] and finding it perfectly capable. Added bonus is an ease of composition of template code – small local-use components can easily be split out within the same file for readability, in contrast with Vue SFC's. Vue even has true "function components" now! [3] (nice to have in really minimal cases)

[1] https://vuejs.github.io/vetur/guide/vti.html [2] https://vuejs.org/guide/extras/render-function.html [3] https://vuejs.org/guide/extras/render-function.html#function...


I love Vue 3, but 99% of the reason I love it is the SFCs and not using JSX/TSX most of the time. Vue can't optimize render functions as well as it can optimize templates, and templates are significantly more approachable for people like me who come from the design/HTML/CSS world and want to contribute to the codebase (especially since most front-end devs coming from JS world suck at HMTL/CSS).

Is typing really the main issue there? Working in render functions and TSX is sooooo much more clunky for doing the same basic things, and you lose all the nice benefits of Vue's SFCs and things like built-in class array logic.


The reactivity system in react is certainly "outdated" when compared to Vue, Svelte, Solid, or pretty much any newer framework. But it's just as you said, the larger ecosystem does have its benefits. There are a few libraries like Framer motion, that don't have equivalent support in other frameworks.


It’s all cyclical.

Before React two-way-binding of data, known today as Signals, was the way we did things. Angular is probably the best example from the pre-react time of that.

React came along with a philosophy of one-way-binding also known as unidirectional data flow. I’ve stuck with it because I think it enables much better local reasoning, and more predictable outcomes.

Neither are outdated.

I’d wager the most performance critical use cases would benefit from signals, things like tldraw.

But unless you’re doing something which needs to paint every single frame (ignoring animation/transitions), the extra overhead and mental load required by Signals is excessive. It’s overly complex and harder to reason about.

With no disrespect to Vue, Svelte, Solid, etc, they feel outdated in the way Angular does.


This is in line with how I was taught how to use React years ago. I'm surprised so many people are angry with these new docs. React has been such a productivity boon for us and every time I have to work in Angular, I'm reminded of how unnecessarily complicated it is for a frontend framework.

Many useEffects can be replaced with event handlers and calculated constant variables


React is still easier to work with than angular, depending on your application, even with these issues. But these are still issues : )


What issues? This is an update to the docs to reinforce best practices that have been around for a while now. People are acting like there's been sudden breaking API changes. useEffect has always been a last resort technique, and even the old docs reinforced this. People saying things like "They recommended Next.js for starter projects, so they're giving up on SPAs?" both don't understand what Next.js is and, really, just want to be angry about something.

The response to this has been absurd. Dan Abramov has been active in the community and transparent about the documentation, and he has gotten direct ad hominem attacks from the same community.

It's just the same story: People that feel lost using React claiming the rest of tech world is fed up with it, and it will be dead next year. I question whether they even do frontend work that much, or are really backend devs forced to do it once in a blue moon. Meanwhile every other library/framework trying to "outdo" react just falls short in so many areas. I welcome the competition, but it's clear that these aren't the members of the community that should be tastemakers for frontend design.


A year ago I was applying for jobs (react jobs mostly) and got a take home project with angular. I shrugged and said why not? I stared at the angular docs for about 2 hours and emailed the recruiter saying “no thanks”. Angular is such a departure from anything I’ve used before. Meanwhile react is just simply component based and if you need things like global state you can add zustand and it all just makes sense. Angular felt like a ton of boilerplate (kinda like class-based react but so much more) and I just couldn’t imagine writing code in it daily.

Later on I got a job in react with typescript and serverless functions and it’s a dream.


Weirdly disappointed, turns out I still need useEffect because I've been using it as intended.


You might not not need an effect.


For the less hyped and esoteric audience of the framework that does not want to go with hooks and remain with a simple dumb model I wish they don't deprecate the class based model but that seems to be already happening: https://react.dev/reference/react/legacy. In few years in the future who knows with what new thing they will come up with. Meh.


Every single one of these examples has a nearly identical anti-pattern using componentDidMount or componentDidUpdate.

There's nothing here that's specific to hooks. It's just people misunderstanding the React lifecycle.


In many cases, we can use useMemo instead of useEffect:

- the function is run immediately (during the render cycle, not after)

- we can take advantage of the dependency array

I think the reason this pattern isn't more popular is that we're taught to use useMemo to memoize a result, but we can also use it more simply … to run a function (during the render cycle) whenever the dependency array changes.

Just call useMemo and don't assign the result to anything, such as:

useMemo(myFunction, [dependency array])


This is underdiscussed IMHO. I refer to the render function calls as "the way down" and then after everything is mounted when React starts calling the useEffect callbacks "the way up".

Many applications suffer from the waterfall rendering problem but even for those that don't, if they are large, they will absolutely want to kick off network calls on "the way down". You can easily add a ton of time to LCP by waiting to kick network calls off until the tree has mounted.

I actually go a little out of my way to make sure as much of the tree as possible can render and mount with missing data. This way the work is occurring while data is fetching in parallel, and then a minimized number of components need to be updated once the data has arrived.


useMemo is really nice for sync effects but it breaks hot reload because hot reload does not call useMemo again. E.g. you can subscribe to something using useMemo (so that it is synchronous and you can use the current subscription value in the current render) and then unsubscribe using useEffect as a cleanup callback. When the component is hot reloaded, the useEffect cleanup callback will be called but the useMemo will not be called again if the dependencies do not change. You can use useSyncExternalStore for that but you need to make sure that your subscription value is immutable or hack around it with refs.


Isn't this exactly what useLayoutEffect is for? https://react.dev/reference/react/useLayoutEffect


useLayoutEffect is called after the render cycle but before the “paint”. useMemo is called during the render cycle (i.e. immediately).

You can confirm this via console.log:

useMemo(() => console.log(1), [])

console.log(2)

… will print 1 then 2.

If you replace useMemo with useLayoutEffect, you’ll get 2 then 1.


This is smart. I guess because the common wisdom you see repeated everywhere is "Never memoize unless you're sure of the benefits". Never thought about using them for any other purpose.


There you go. I thought `key` was something you just needed when creating child elements, so react knows which ones are which. I didn't realize it could be used to create a new sandbox for state. But actually it makes sense! Because if you have a list of child items each with it's own state then React needs to manage that using the key. I never made this connection.


My concern with using `key` for this is that it’s somewhat of a booby trap for future consumers of the component. It requires some kind of documentation that you need to hope they’ll read. TypeScript won’t help you. It’s not common enough that they’ll think to go looking for it. It’ll break silently and they won’t know until there’s a problem. Is the best practice to wrap a keyed-to-reset-state component in an unkeyed component and then exporting that? Seems like an odd pattern.


Not necessarily, the component itself can work without a key. Adding a key is just one approach to resetting component state from a parent. If a component can’t work properly without a key it should be wrapped further to make it usable rather than documenting it for all consumers.


That last bit seems like a recommendation that should be in the docs. It’s sensible from a “don’t leave boobytraps” perspective but it won’t be intuitive to many people. It’s one more little rule to learn, another comment on a sizable chunk of PRs. While it may be idiomatic and sensible JSX, it seems like an outlier for React, and I wonder if there’s an opportunity to improve the developer experience of this very common use case.


I think I agree. I commented a while back I prefer components that don’t reset state based on props anyway.

Use state as little as possible and keep things pure and you probably won’t need this.

Example: date picker only keeps the open/close ui state of the picker. The date is a prop and date change is an event.

Instead of original date being a prop. Current date being local state.


It’s useful to think about key as the telling React of the identity of a component.

Whenever you want a stateful component that can be identified by a unique name, you’ll likely want to tell that to React via key.


> They let you “step outside” of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM.

This doc is generally on point and a very important lesson to drill into the heads of people new to react, however that particular phrase is completely misleading. React is absolutely pushing you to use lifecycle effects where necessary. Calling that "stepping outside" is just dishonest.

React is not a "pure view library" that you have to integrate with state management. It makes you handle the extremely impure chain of (route change, component "mounting", data fetching...), all in the rendering code.


> route change, component "mounting", data fetching...

This should really be:

route change -> data fetching -> rendering

After the data is fetched, it should just be assigned to a state somewhere, and the React should, well, "react" to the state change. This may seem a bit complicated for simple cases, but keeps the execution flow explicit and "steppable" though the debugger.

We use MobX for the "react" part; I'm sure this is not the only way.


Yes, but that is The Elm Architecture. React's docs guide you towards using react-query and react-router, making everything lifecycle bound and the renders impure.

https://react.dev/reference/react/useEffect#what-are-good-al...


> Calling that "stepping outside" is just dishonest.

I don’t think it’s dishonest. There’s two main phases in react, render and commit.

Render is interruptible. For example when a new event like an interaction comes in, the in progress render might get chucked out and restarted with the new information.

Commit isn’t interruptible. It’s a sync phase. This is where all the effects run, and the purpose is to take the new state from the render phase and synchronise it to the external systems. Aka cause side effects.

The useEffect hook running in the commit phase, does let you step outside of the react framework. It lets you safely interact with external systems. If you tried to do any of that in the render phase, you risk getting cut off halfway, or doing things out of order.

> React is not a "pure view library" that you have to integrate with state management.

A view library without state management would be a templating engine right?

> It makes you handle the extremely impure chain of (route change, component "mounting", data fetching...), all in the rendering code.

It lets you define how to respond to those changes in the render phase, but they aren’t applied until the commit phase.

And that means you can wrap up the complexity in well tested components that provide a simple API.


I'm still confused. They present an example where they load data from local storage but do not make it clear how that data is passed out to the app.

What if I'd like to always initialize my app's state with the data in local storage? Let's say I want to initialize from localStorage and pass this to context. Doing so triggers a hydration error in React v18 because the server-side value will never match the hydrated value once set. It seems like a job for useSyncExternalStore since that's what localStorage is (an external key-value store), but the ergonomics of the subscribe function seem odd to me.


I despise the prevValues paradigm that crops up. Its even recommended in these docs, under "Adjusting some state when a prop changes"

Having to keep a second variable around for every prop you need to compare is just miserable to work with. useEffect is less efficient, but so much more obvious in a situation like this. "I want to do something when this value changes, and ONLY when this value changes" seems like something React should be able to handle elegantly. It happens literally all the time.


I like these types of articles, I come here and do Ctrl+F "svelte" I'm looking to see who is brave enough :)


For my personal projects it's always Svelte now. Fits how my brain works. I used to love React, but I must be getting old now and wish to have as little complexity in my life as possible.


Sometimes I Cmd+F "elm", but only on days where I feel like picking a fight.

ducks


Yeah, Elm solves all of those problems through its purity-enforcing compiler. It's a shame so few people know about it.


The surprising thing to me is how many JavaScript enthusiasts love to bang the FP drum but refuse to consider Elm.

It makes no sense at all.


Because Elm is functionally dead. Core libraries? Not updated for 2 years. Core compiler? Not updated for 7 months. It's only Evan, who is seemingly burnt out, and hasn't handed over progress to anyone else. A quick glance into the Elm community will surface this. Feel free to nerd out on an esoteric approach but all the other products coming out are generally shipping fast and bouncing approaches off each other using Next.


It's unfortunate that the current state of affairs in software engineering is that if someone doesn't merge a bugfix PR immediately, rumors of its death begin to circle.

A quick glance at the Elm Discourse or Elm Slack will surface that our community and culture is alive, well, and growing.

Evan continues to quietly work on vnext, and is presenting at Goto Aarhus in May.

I'm not interested in changing your mind in particular, and I do not wish to argue; I am merely posting this for the benefit of anyone that should see your comment.

Sometimes, shipping fast and bouncing approaches all over the place is not, as it happens, the most sane and sustainable way to create lasting software. You and others can continue along doing that; we will continue to ship our hundreds-of-thousands-of-lines-of-code Elm frontend.


Wait so in the prop change example they are now recommending that you can update state during a component render?


Yep it can be a useful pattern, but it must be in a conditional to stop an infinite loop.

This useful guide points it out as well: https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...


I found this one very surprising. I’ve operated for years with the understanding that side-effects should never ever occur within the render flow. Complicated as hooks are, I dread having to explain the nuances of this particular one to other React devs.


Yes, and they explain it as well. Doing that leads to the component re-rendering immediately without rendering any child components.

This should be faster than useEffect which only executes after the entire render has been committed to the DOM.


This caught me off guard. I've never seen this pattern anywhere including popular 3rd party React libs. Therefore it doesn't feel right but their explanation makes sense.


Far as I see they aren't. They're recommending you don't use unnecessary state. From the example, if you have state for `firstName` and `lastName`, you don't need another state for `fullName`. Just calculate it during the render as `'${firstName} ${lastName}'`


They're referring to https://react.dev/learn/you-might-not-need-an-effect#adjusti..., where they literally call setFoor in the render method.

I guess this makes sense, but it's weird after all the "never do anything stateful in render"


Wow, that's really weird.


I think we've reached the point where React is ready to be challenged by a new competitor. Unfortunately I haven't seen one yet.


There are currently two new web frontend frameworks that are sharply on the rise: Svelte and Solid.

Both do away with much of the complexity of React.


If you'd consider an old competitor, then Elm does everything better and is much easier to learn.


Too closed off to the world. You can't win over the world if you are closed off to it.

Elm has been going nowhere for ages now, and will continue to go nowhere, as long as they keep not listening and don't give people what people ask for.


As far as I can tell, people keep asking for runtime errors. That would defeat the purpose, and turn Elm into yet another JavaScript-like foot gun.


LLMs may well obviate the need for further JS frameworks - at least ChatGPT seems to be just a capable of generating pure JS vs framework-based JS from what I've seen so far. At any rate the sorts of things it's good (and not good) at doing don't seem to vary with the choice of framework. Having said that, as long as there's an expectation the generated code still needs maintenance by human devs, there may be a reluctance to step back from frameworks.


This is a footgun I found at a few Amazon internal apps recently. I was able to clear away probably 3/4 uses of useEffect--some of which I was guilty of adding.

Given how common this error is, there is definitely an unmet need. What people want is a listener, ie.: - recalculate a var whenever a prop changes (this is what useMemo is for) - trigger a function when a state changes (should be moved to the function that changed the state)


That is not what useMemo is for. It's for caching a result and only recalculating it if the dependencies have changed. The aim is to optimize performance. It is not intended for listening to prop changes.


You might not need React


You might not need to write drivel comments too


You should lead by example.


The 8 votes I appear to have on my comment demonstrate that I'm not alone in this. Pointing out pointless comments is not itself a pointless comment, but who am I to argue with an expert in drivel comments?

Feel free to refer to the HN rules on comment quality.


useEffect is absolutely core to doing to react development with hooks. Saying "you might not need an effect" is like saying "you might not need the + operator", like yeah, sure, if you're not trying to add two numbers or concatenate a string or whatever you won't need that operator, but in like 99.999% of all applications you will need it.


That's really not the point of the article. It's addressing overuse of useEffect(), not implying that you never need it.


I think my gripe here is the casual use of “you might not need…”. It’s inspired by the “you might not need redux” article by Dan Abramov, but the difference here is that while you might not need Redux, you most likely will need useEffect. They’re just not same in that regard.

Maybe I’m just being pedantic here, but I think how you use words matters, and in this instance a better title would be “avoid unnecessary useEffect calls” or something like that. “You might not need an effect” is just poor (sloppy?) wording, because it’s usually objectively wrong.

The reason it matters in this instance is that with the current title I’m going into the article thinking I’ll learn some new way to do effects which is better than useEffect. But the article offers no alternative to useEffect. You will need useEffect.


In both cases (this page, and the one you mention) I think you’re inferring the wrong context — which is reasonable because it’s a legitimate inference.

I think the context is “for this use case”. Neither are saying never use Redux or useEffect, but are more about the common problem of people reaching for solutions that aren’t a good fit for their problem.


I think I disagree, or at least we might disagree on what operations are common in React.

When I have seen useEffect used, it's most often uneccesary. People are using it to create dependent state (ie. when x changes, then recalculate y) or to handle the result of an event.

It's really only needed in components that handle fetch responses, which IME is much fewer than 99.9%.


Not 99.9% of components, but 99.9% of applications. Of course I haven’t actually done the numbers, but if your application fetches data or listens to browser events you will need useEffect. I’d estimate 99.9% of web apps using React do at least one of those things.


There is only one case where you should be fetching inside of useEffect, and that is with an empty dependency array to get data on first render. Pretty simple and easy to wrap your head around.

Everything else is triggered by event handlers. I work on a 70Loc react app and useEffect is used maybe once outside of this context. You really don't need it.

And I don't know what you mean by "listening to browser events" with useEffect. You really just need to use onClick and onChange.

You also don't need it to set state, you need calculated constant assignments.


We need articles like these because people don't understand that React is fundamentally an idempotent framework. The function component or render method, well, renders every single time props or state change. Hooks are just a way to have some higher order state and if you think of React as UI = f(state), then the state variable also captures hook state as its input. It would be more clear to say UI = f(state, useState variable 1, useState variable 2, useEffect function 1, ...).


  //  Good: This logic should run because the component was displayed
  useEffect(() => {
    post('/analytics/event', { eventName: 'visit_form' });
  }, []);
And then a wild 'exhaustive-deps'[0] appears!

[0]: https://github.com/facebook/react/issues/14920


There aren't any function-scope variables used here, so an empty dependency array is correct.


People talk about the benefits of a functional architecture with UIs and how I guess you can see it as a data stream where FP helps think about things, but surely Smalltalk-style OO with messages would be the best way to represent something that both requires a lot of back-and-forth but also widget state? Are there any OO-style web frameworks that are popular nowadays?


Does Apple's preference for the Delegate pattern touch on this?


huh.

well I’m lost then.

glad they’re writing this up. I come to react after hooks was introduced, and have been kind of put on a pedestal for not needing to “learn hooks” since thats all I know, for React.

still looks like I need to relearn something.


Using a key to reset state when props change is a great tip. I was using an effect to do that.


It's really tiring to keep seeing the bashing on the effect on hn. It's an advanced tool to replace hoc hell where you need reusable shared logic. In the doc it's even stated as such and also stated that it's for cross cutting logic. If you haven't seen hoc hell before when using classes, that only means your usecase was not complex enough for this kind of tools


this is great. i learned some instances where i've been improperly using effects.

there are a ton of tutorials out there that teach new react devs the wrong patterns. really low bar of entry to put some article up on medium or dev.to about "useEffect tutorial". and then those devs go on to write their own tutorials that are misguided, etc


Why is all of the text on this page bold?


Heavy fonts are the new "grey font on grey background".


useBoldEffect() ?


That's deprecated.

It is now useStrongEffect()


You might not need react.


React has jumped the server side shark.


awesome! I've always wondered why people relied on useEffect so much. I've been using it perfectly


It says not to use effects, but then suggests useMemo which is an effect!


I'm not sure that useMemo really is an effect in the sense that useEffect is. The useMemo callback is invoked synchronously during render, rather than getting pushed onto an effect queue like useEffect does.

Even useLayoutEffect goes on an effect queue, and if it results in changes, will need to render a second time, while a changed memo calculation happens entirely during the single render pass.




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

Search: