Hacker News new | ask | show | jobs
by alpinisme 439 days ago
There’s no denying that the idiomatic solution is sometimes far from obvious, but idiomatic react wants useEffect to only about synchronizing react with external systems. Everyone reaches for it to synchronize between components and do all sorts of other non-idiomatic things though, and that’s where the pain comes in.
2 comments

A lot of the time it comes down to the platform not actually providing easy ways to do things that users are reaching for effect for.

For example some people reach for it for fetching data in routes. It wasn't until recently that routers started to come with built in patterns to do this without effects - example being react routers (it's been a while since I used but remix had this) loaders and tanstack routers beforeload and loaders.

People fetched in an effect because at the time this was the most simple and obvious place to do it unless you were very aware of the other ways to do it, and we didn't have primitives like react query either.

Another example of a non obvious things logging an analytics event when a specific component renders. You could attempt to tie this to the specific update that caused a component to show but that update may be very complex or hidden since your component could show based on a variety of states and require so much more work vs just logging in an effect.

I guess one could argue both of these themselves are syncing operations, syncing your network state and server state for API requests in routes and analytics. But at the same time reacts team told everyone if you're fetching in effects you're doing it wrong without providing the answers.

That to say yes effects are not a very good pattern for many things we use it for and should be avoided, but react as a framework (yes it's basically framework in my opinion, we're well past just. library point) itself does not educate well or have easy to understand built in ways to do these things that are VERY common use cases

And this as someone who writes mostly react.

I think everything you said is 100% true. But the latter half is also true - React has (for better or worse) always been a view 'library' and has very little opinion on syncing network state/api requests.

Unfortunately, many people use inappropriate levels of abstraction (useEffect and/or redux) and it becomes an architectural problem.

Out of curiosity, I just looked through some of my old code calling useEffect. Most of it was for fetching data from an API on mount; I'd also written a custom little hook that returns a callback to signal that the data should be refreshed.

But a few instances were to conditionally set one piece of state whenever another piece of state was changed, arguably an abuse of the mechanism. I suppose the proper way would be to wrap the setter function into one that changes both, but it takes a fair bit of discipline to avoid useEffect in the heat of the moment.

There’s an excellent article in the React documentation about this (“You Might Not Need An Effect”). Back when I was working on a React team I probably threw it at a code review on average once a week.

I really like React, but given the way developers seem to struggle to use it “correctly” (despite all the lint hooks and weird diagnostics like double rendering to help) it’s hard not to feel like there’s something wrong with it.

> But a few instances were to conditionally set one piece of state whenever another piece of state was changed

That use case is explicitly called out on the "You Might Not Need An Effect" article in the docs (which everyone writing React should read, and arguably was published years too late): https://react.dev/learn/you-might-not-need-an-effect

TLDR:

When updating a useState based on another useState, don't use the first useState at all, just compute it inline. If it's expensive, wrap it in a useMemo.

When updating a useState based on props, call the setter directly from the component function, and React will immediately re-render the component (instead of rendering it, running the effect, and then rendering it again).

Believe me, I've read that essay from start to finish, but as I mentioned, it was in the heat of the moment and I didn't have much time to architect something more proper.

> When updating a useState based on another useState, don't use the first useState at all, just compute it inline. If it's expensive, wrap it in a useMemo.

Well, the problem in this case was that the affected useState was not just a pure function of the useState that caused it to be modified: other actions could modify it as well. (E.g., you have some form state that should get updated whenever a state value somewhere else is changed.)

I believe useReducer is a bit closer to the use case I'm thinking of, but the dispatch functions for that are verbose and unpleasant to write. Presumably ad-hoc closures wrapping the setter functions would be somewhat more lightweight.