Hacker News new | ask | show | jobs
by LegionMammal978 439 days ago
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.

2 comments

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.