A Visual Guide to React's useEffect (2021)
alexsidorenko.com
alexsidorenko.com
- Updating state based on props or state
- Caching expensive calculations
- Resetting all state when a prop changes
- Adjusting some state when a prop changes
- Sharing logic between event handlers
- Sending a POST request
- Chains of computations
- Initializing the application
- Notifying parent components about state changes
- Passing data to the parent
- Subscribing to an external store
- Fetching data
Effects should also be idempotent, they should include pure functions that don't have side effects (such as doing a POST request) which Dan Abramov of the core React team addresses [1].
[0] https://beta.reactjs.org/learn/you-might-not-need-an-effect
[1] https://twitter.com/dan_abramov/status/1281669881667162112
useEffect WAS for side-effects. That’s why it’s named that.
That’s literally the first sentence in the Effects hook page in the current non-Beta documentation.
> The Effect Hook lets you perform side effects in function components:
https://reactjs.org/docs/hooks-effect.html
I completely agree that useEffect should not be used for side effects. The concept of using it to sync with an external system makes far more sense.
But this is a very different and new understanding of what useEffect is. The React team, for whatever reason, does not present it as that. They do not point out that, yes, this was originally supposed to be for effects but they realized that this was a bad idea and have refined how they believe it should be used.
Instead, the documentation and commentary around such changes never point out they are changes and just present them as how things are. Which leads to a tremendous amount of confusion among new developers learning React, because it’s never clear what the actual correct approach is, since you rarely come across commentary in a temporally ordered way.
If the Beta docs included the statement “we used to recommend this for side effects, but now don’t”, any newbie reading that will forever understand why the highly upvoted SO answer from 3 years ago recommends using useEffect for a certain side effect but today, that is probably not the right solution.
React 18 introduced a new hook called useSyncExternalStore for that, which implies useEffect isn't quite right for that purpose otherwise there'd be no need for a new hook.
You can of course wrap your GET/POST requests to an external network API too but that seems like a lot of unnecessary code to pull in external data.
If you don't need it then you should be questioning whether you even need React, not whether or not you need useEffect.
Think your last reply overlaps with the first half of my message. I reach for swr or similar to manage the lifecycle of client side network requests (or any async requests) if necessary. I would imagine that these libraries or new ones adopt useSyncExternalStore internally. If I want to use this hook myself, it seems quite a bit more work to have to write a subscription-based store just to make certain network requests. Many apps aren’t real time either.
Take a single case: analytics. I want it to trigger whenever the page loads (and can’t be done server side for whatever reason). Are you saying that I need the kitchen sink to make this fire-and-forget request? useEffect is fine here. Am I not making an external request well? I just read the useSyncExternalStore RFC. Its primary purpose is to handle external state change in a concurrent environment. Makes sense. Is it to become the default for all “external requests?” Idk. But let’s agree that not all requests require the same care.
Why not do that in your setState function?
React was a breath of fresh air when it first arrived and I'm grateful to it for rekindling my interest in front end javascript but the cracks are starting to show. It's just way too hard to write correct, performant React code. I daily see veteran programmers with years of React experience under their belt surprised and confused by the way their React code is behaving.
Doing those things is not completely wrong, there is just a better/easier solution.
This is so true.
I've had similar conversations many times with coworkers before when they were using `useEffect` to keep a state value in sync with a prop. The officially-recommended alternative of manually storing and updating an extra piece of state containing the previous prop value is cumbersome and also had ways it can go wrong. So, since `useEffect` works well enough in most cases and is easier, often the code just sticks with that method. I'm not entirely sure what's really best all tradeoffs considered, but it definitely illustrates how rough edges often pop up in hook-based React.
Any of these sorts of hacks tend to be indicative of something not being structured poorly.
The React docs are very clear on this:
> The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser.
https://beta.reactjs.org/learn/you-might-not-need-an-effect#...
I guess they did a bunch of work to make the case work and are confident about it, but it just seems like such a weird decision considering they’ve been adamantly telling their devs not to do this for years.
From the original hooks FAQ at time of release:
- https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-g...
I talked about the reasoning / behavior for this in my React rendering guide:
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
It used to explicitly be a thing you must not do for years, it’s weird to go straight 180 on something you actively warn developers not to do. If it wasn’t well documented in the past, then that’s reasonable, but actively spitting out console warnings and explicit documentation warning against it is just going to mean that anyone who isn’t new to react is going to be extremely weary about it.
For 9 years in a row we discuss core concepts of the library like it's some dark art that only select few will ever understand. It's really not that hard.
If it's legal, the linter should allow it. F$@k "consistency."
Aren’t useState and useEffect the two main building blocks for hooks (besides useContext)? Everything is derived from those two.
So I don’t get how it would be wrong to handle any of those things with useEffect. It may not be the most easy to read code, but in the end it’s all useState and useEffect under the hood.
Where is this said? Because everything I've read has been that doing any non-idempotent action in an effect will cause bugs. Therefore, GETting data is fine but POSTing it is not. I've read this even before these new beta docs.
However most modern frameworks use something like React Query, GraphQL, etc which handles synchronizing local state with remote data for you.
This "3rd party library by default" bullshit is killing me. I just read that link, it's basically "here's a general problem with data fetching that every front ender should thoroughly understand OR actually you know what just use a fucking library and forget all about it".
The React docs have been full of this rubbish for a few years now and it's just straight up irresponsible. Worry about what your own library does, and if there's footguns or complexities then explain them thoroughly. Don't just handball them off and treat your API consumers like children.
I've noticed that new developers tend to think of libraries are indivisible units. They never stop to think what React Query uses, it's an atomic unit that you use to do queries, duh.
In reality React doesn't expose public APIs for creating your own useEffect; just the base hooks you can compose into "composite" hooks.
The one talking about fetching data recommends a 3rd party library to make it simple but tells you how to avoid certain issues if you want to go the route of implementing your own fetching.
The POST example talks about how some people will modify a state variable to tell useEffect to send a POST request when you can just do it directly.
Maybe it's not to the depth of your liking but I wouldn't call them rubbish.
yeah, making it into a custom hook lol.
When did something that was originally worth 5 LOCs turn into 20 LOCs
When you want it to work properly in all the cases. That includes when new data is being fetched and you don't want to show the old data. Or when you want to not refetch data if the conditions for refetching are not met. Or when you want to cancel an in-progress data fetch if the data shall no longer be shown after fetching it because the user closed the component tasked with showing the data.
I bet your 5 LOC can't do some of these things.
This only really happens when you have race conditions
> Or when you want to not refetch data if the conditions for refetching are not met.
This only happens when you fetch based on state update on the same component
> Or when you want to cancel an in-progress data fetch if the data shall no longer be shown after fetching it because the user closed the component tasked with showing the data.
you can do this with certain conditionals and a cleanup function
over 3 quarters of my data fetching is just a simple fetch on onComponentDidMount
do you mean to tell me I have to bring in a library just for that?
Or when the network is unpredictable, you retry because it's taking too long, then the second request succeeds, then somehow the first request succeeds.
> This only happens when you fetch based on state update on the same component
Or on props update.
> you can do this with certain conditionals and a cleanup function
That's no longer 5 LOC.
> over 3 quarters of my data fetching is just a simple fetch on onComponentDidMount
Congratulations. Do you also do that for authenticated requests? If so, either you are using some kind of hardcoded global variable, or you are not using 5 lines of code.
> do you mean to tell me I have to bring in a library just for that?
Of course not. You can implement your own library for that. Or copy-paste it all over the place.
What I'm saying is that for things that are more advanced than basic data fetching, you may want to bring better tools than just fetch + 4 extra LOC.
This has an atomically low chance of ever happening, and never happening on single threaded monolithic servers.
>That's no longer 5 LOC.
Okay cool, 7-8 LOC, better than abstracting it into a whole new custom hook.
>Congratulations. Do you also do that for authenticated requests? If so, either you are using some kind of hardcoded global variable, or you are not using 5 lines of code.
You need to be more specific about authenticated requests, because cookies that are sent back as a response will have directions on how they should be sent back to the server on each request that requires no javascript at all.
If you're using token auth with localStorage, which probably isn't a wise idea because you're now susceptible to XSS attacks, then you should already be abstracting that away.
>What I'm saying is that for things that are more advanced than basic data fetching, you may want to bring better tools than just fetch + 4 extra LOC.
here's a pastebin of my wrapper around requests
it is already highly configured and abstracted.
All i want to do in the useEffect is await api.getGamesByDate(), and this should be fine.
What you're suggesting is that you support the React team's decision in that I have to have an additional wrapper around my apiCalls just to avoid two fetch calls in Strict Mode.
Does that sound logical to you?
Once you have setup that library (or hook) yes, using it it's indeed 4 lines of code. That also happens for libraries constructed by others.
imo most projects do not need and are made worse by that bloat.
It's light enough to be worth it from a very small amount of API calls. Having caching and loading states is a significant UX bonus very quickly.
Anyway, yeah it might be a code smell if everyone is using them wrong, but I'm not sure how the React team could fix it, at least without breaking changes or moving to some other paradigm.
At least part of the “wrong” use is the recommended best practice has evolved over time, so following the recent recommendations would be “wrong” by the current recommendation.
React is pretty nice overall, just why do we design things to be so unintuitive. Dark patterns.
Which is a huge win IMO because the whole class/object paradigm in JavaScript is broken, and tracking what ‘this’ might be is literally impossible.
I think that’s sufficient reason to use hooks.
Hooks bring a completely new paradigm and I think it was brought out of Beta too early, so React now has to stick with certain concepts that appear bad ideas in hindsight (a fairly simple and relevant example would be that running useEffect on every render should not have been the default…I suspect the React team could have swapped the behaviors for empty array dependency and no dependency parameter, and it wouldn’t be any more logically weird than what we have today and the default behavior would have been far less footgun-ny).
I do think the general idea behind hooks is excellent. I think some of the existing choices, however, need a significant rethink, with the additional real world experience the React devs have with it now.
I chuckled. `this` is a piece of cake to understand compared to hooks.
`this` is a function argument that is typically passed to the function by placing it left of the dot at the time when you call the function. That's all you need to know - now you understand `this`.
Hooks are a whole nightmare in comparison - there is a stateful counter that assigns an index to every `useState` call just to determine which result you need to get back. This is a terribly error-prone design that passed review with lots of dubious justifications.
And if they really wanna go there, it was the React team itself that took away method auto-binding when they forced everyone to use `class` keyword. Mixins were already available in `createClass`, and if they wanted privacy between mixins, that's totally do-able with Symbols.
myStringNumbers.map(parseInt)
Because of this I find it very smart to tool up with opinionated linting rules + TypeScript. Won’t catch everything but it covers a lot of easy mistakes that exist everywhere.https://docs.python.org/3/library/functions.html#enumerate
https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho...
https://docs.julialang.org/en/v1/base/iterators/#Base.Iterat...
Amusingly in Haskell it only takes nine characters to define enumerate, so there’s not much benefit giving it a name!
enumerate = zip [0..]
https://stackoverflow.com/a/6473153/119271Very tangentially related, Apple's Metal API has a function that copies a texture. You pass it a width, height, etc... But, if the texture is compressed, then 255 of 256 possible value combos you pass it will be invalid since compressed textures can only be copied in block multiples. I think it would have been a better designed function if it only took width and height in blocks instead of pixels (with uncompressed textures defined has having 1x1 pixel blocks). Then this nonsense of 255 of 256 values being bad would disappear. There's a ton of other inconsistencies in that function. For example, you pass it destinationBytesPerRow when copying to a buffer but if the texture is compressed you pass it say 40 rows and it will only only actually copy 10 rows of blocks and only advance the destination every 4 rows instead of every row. It's arguably a poorly designed function. Thought, I suspect it was inspired by similarly poorly designed functions in other graphics APIs
Just use an arrow function to wrap the callbackFn.
I think a big part of the problem is we need to stop selling useEffect as a replacement for the lifecycle methods of class based components. It also probably would've been a lot less confusing if we called it something like useSideEffect
I think any react linting setup resolves most of the confusion but there's a lot of people that start off and don't even know how to set up lint rules for react. They should be a default in any react app imo
I’d say its the opposite; people who used lifecycle methods know that useEffect and friends eliminates an entire class of bugs. People who started with hooks dont understand the problems it solved, and only see the quirks
You could have this API
this.addListener('mount', () => {
// do things on mount
// return cleanup to be called on unmount
return () => cleanup()
})
called from the constructur of a component. With this you could setup multiple listeners and make sure they're all cleaned up.But no, the syntax wasn't "clean" (you'd pass `this` as an argument to the "custom hooks" equivalent"), so instead we got this error-prone order-dependent design that doesn't allow conditional execution and runs on every render.
The part I don't like about useEffect is that developers tend to overuse it and when they get stuck in infinite render loops you can see the whole mess it can lead to and how hard it can be to untangle monkey patched logic.
It makes perfect sense from inside of useEffect.
If there are no dependencies ([]), the dependencies can never change, so React can always reuse the old effect.
If the dependencies aren’t defined, there’s no way to tell if the old effect is OK, so React must always rebuild it.
**
But `useEffect(()=>{})` doesn’t explicitly show that React will get `dependencies = undefined`, and in this case that’s unusually important.
You could have ESlint force you to change that to `useEffect(()=>{}, undefined)`, I suppose…
For useEffect, exhaustive-hooks & Typescript say nothing, because a non-memoised Effect is a reasonable thing to write. It’s just not a great thing to write accidentally.
I have yet to see an in-depth treatise on how it works under the hood. All I get are surface level posts of guidelines and crappy posts on FreeCodeCamp written by HS students who learned how to program yesterday. State management is another nightmare as well.
Check out “A Complete Guide to useEffect” [1] by Dan Abramov, one of the core React developers.
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
as well as these other excellent posts on React's internals and implementation:
- When does React render your component?: https://www.zhenghao.io/posts/react-rerender
- A Complete Guide to useEffect: https://overreacted.io/a-complete-guide-to-useeffect/
- Getting Closure on React Hooks: https://www.swyx.io/hooks/
- Didact: Build a Miniature React with Hooks" https://pomb.us/build-your-own-react
Given the dominance of react, "switching to vue" (let alone svelte/solid) is not a viable option for many. But surely someone can shamelessly steal from them?
"Not to be compiled" is not a feature. It is a lack of feature. Would you say it is better for everyone to ignore typescript and use js directly?
There is obviously something to be said against hooks being "functions, but with special requirements", and things like React safe mode actually exacerbate the problem (it used to disable console.log on the double render, leading to variables literally changing under your feet; now you just get double console logs for unexplained reasons).
I'm curious if the React team experimented with an alternative set of lifecycle methods for class-based components. Like, add a new base Component class, but have the constructor freeze itself to outlaw instance variables, and require going through a useState-like API to get that functionality. What would it look like?
While we are at it, I would suggest to specify in the submission title that it's about React, and it's also from 2021, so:
A Visual Guide to React's useEffet hook (2021)This would allow us to avoid all the gotchas with non-pure functions in useEffect, breakage when calling hooks in conditions or loops (hoist them!), and whatever restrictions might come next.
I tried to drink the Kool Aid, and I got paid to write it, but it's just a bad idea to build a parallel abstraction, that leaks like a sieve, that needs its own browser tools, that pretends to be reactive but really isn't. I was so much more productive when I used vanilla js, plus the odd library as needed. And if you tell me react is "just a library" - great, remove it from your dependencies and tell me what % of files you now need to fix, because I've seen multiple react projects in the wild and they all look the same.
The non-React code works on its own and is super easy to understand. You could even extract the business logic and make it a CLI or a reusable library.
Shit, all my apps - Java, C++, whatever - have always been structured like this: domain logic + a decoupled frontend. I pretty much only adopt libraries that match my way. Redux did not match my way
My coworkers from past jobs tell me the way I structure my projects is so clear so I think I’m on the right track
Do you have any such experience? How do you architecture your models, events, processing, updating, etc? Do you have UI-less component-like pieces of code that you can compose? Or a fat root state manager that does everything?
Not sure if you work on any public repos, would love to have a look if you do for inspiration :)
First I want to note that I do use React hooks. If I can some UI code in React, I will. DOM code would go in React
I just won’t put business logic in React ever. No non-UI side effects and no external state are allowed in my hooks
I still want my React components themselves to be a reusable library. My applications are basically a reusable logic library coupled with a reusable UI library. It’s that division that makes this style easy to read - your brain is either in UI mode or business logic mode. You never confuse yourself trying to figure out what it’s doing because you can just read one side and ignore the other
Whether you use a fat root state or not is honestly up to you. It’s really the separation that is key
I’d check out mobx examples
I know that’s not super specific and there are edge cases that you’ll run into, but my public repos are either Java and non-UI JS libraries ):
Keep any and all business logic out of components if at all possible; that's my moto.
Personally, I like the way react is balanced. Yes, it can feel unintuitive or complicated to beginners, but that's because it puts a lot of emphasis on experts productivity and ergonomics while only using js, not being compiled.
Once you do things the "react way" (whatever that means, you'll get there if you take time to identify and eliminate "smells"), it just feels so... smooth? You can build everything with the same methodology and flow. Simple components can feel a bit over engineered, but the hard ones ones feel much simpler than they would be in other stacks, and upgrading code and functionality feels effortless.