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.
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?
If it's legal, the linter should allow it. F$@k "consistency."
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.
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.
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.
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.
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.
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.
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.
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.
- 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