Professional Documents
Culture Documents
Posted on 1 nov
• Updated on 4 nov
The useState and useEffect hooks were a godsend for the React community. However,
like any tool, these can easily be abused.
Here's one an example of one misuse I've seen a lot in my tenure as a software dev:
useEffect(() => {
someAsyncApiCall().then(res => {
setData(res.data);
setLoading(false);
});
}, [setData, setLoading]);
useEffect(() => {
setItems(data.items);
setItems(data.items);
setItemsLength(data.items.length || 0);
return (
// ...JSX
);
};
The problem with the above use case is that we are keeping track of some redundant
state, specifically items and itemsLength . These pieces of data can instead be derived
functionally from data .
A Better Way:
Any data that can be derived from other data can be abstracted and re-written using
pure functions.
return data.items;
};
return getItems(data).length;
};
useEffect(() => {
someAsyncApiCall().then(res => {
setData(res.data);
setLoading(false);
});
}, [setData, setLoading]);
return (
// ...JSX
);
};
Takeaways
The cool thing about this pattern is that getItems and getItemsLength are very easy to
write unit tests for, as the output will always be the same for a given input.
Perhaps the above example was a little contrived, but this is definitely a pattern I have
seen in a lot of codebases over the years.
As apps scale, it's important to reduce complexity wherever we can in order to ward off
technical debt.
tl;dr:
Using useState and useEffect hooks is often unavoidable, but if you can, abstract out
any data that can be derived from other data using pure functions. The benefits can
have huge payoffs down the road.
Discussion (16)
The first approach is indeed wrong, because you should use useMemo for derived state,
instead of useState+useEffect, the second approach is dangerous since it can lead to
slow renders and infinite re-rendering, so if you are doing complex manipulation and/or
passing the derived state down to another component you should use the derived state
factories and place it inside a useMemo
If the calculations are expensive, then yes, useMemo would be ideal, however most
derivations are quite trivial in my experience and using a pure function to compute
takes less effort and cognitive load to set up. Also, pure functions are inherently easier
to test as well. Thanks for the feedback!
You can declare the pure functions exactly the sema way, and test them as well, just
wrap them inside the useMemo when calling, for me the golden rule is: if you are
experienced, check your render times and render counts to decide using it or not, if
you are a beginner and you are not sure always wrap derived state inside useMemo,
because the using it when it is not needed will be negligible, but not using it when it
is needed will give you so much headaches
leob • Nov 2
I suppose "dangerous" only if those calculations are slow? if the time to execute them
is negligible then useMemo would probably be overkill ... otherwise then yes, it would
be the recommended approach :)
WEBKADABRA • Nov 3
useEffect(() => {
setItems(data.items);
setItems(data.items.length || 0);
useEffect(() => {
setItems(data.items);
setItemsLength(data.items.length || 0);
Good catch! Made the correction. Important to test first before sending off to QA 😅
devmeqa • Nov 4
I'm new with React and I don't understand why isn't this code triggering an infinite loop
? (we watch for changes in setData and setData is called within the useEffect)
useEffect(() => {
someAsyncApiCall().then(res => {
y p () ( {
setData(res.data);
setLoading(false);
});
}, [setData, setLoading]);
UPDATE - see follow up below.
Your concern is 100% warranted - useEffect dependencies are often the cause of
infinite loop headaches, mainly from objects, arrays, and function expressions.
The set* functions returned from the useHook call reference the same functions
between renders. Or, in other words:
[useState and useEffect] are guaranteed to have a stable signature between renders
// object
UPDATE - I think that React does some smart memoization for the dependencies, so
my analysis is incorrect. The main thing that would cause a useEffect is performing a
setState (the same as using one of the set* useState functions) inside of a
useEffect , where the state value is part of the dependency array.
This is a good article that outlines some of the pitfalls and how to solve them:
dmitripavlutin.com/react-useeffect...
Good article.👍
Though, there's one thing that I gotta mention. You don't need to include the state
setters functions that are returned from useState in the useEffect dependency array, the
p y y
reason being
that these function are guaranteed to have a stable signature between renders.
Good catch! I think the exhaustive-deps eslint rule used to complain if setter fncs were
not included, so I just got into the habit if included everything in the dependency
array, but you're absolutely correct (and eslint plugins have gotten a lot better over the
years).
You can use optional chaining in your conditional checks now, instead of multiple ||
conditions
Old habits. :) I use optional chaining regularly in TS files, but didn't know it had such
universal browser support now. Thanks for the tip!
developer.mozilla.org/en-US/docs/W...
eBitzu • Nov 2
You should make a custom hook when dealing with any type of data...
Agree, I use custom hooks a lot - it's also important to not abstract things out too
hastily. I'm a big fan of iterative refactoring.
Code of Conduct
•
Report abuse
WORK
Senior Software Engineer
JOINED
30 oct 2021
Do you own a domain name without a purpose? Let me give you a project for it!
#watercooler
#discuss
#webdev
#startup