You are on page 1of 7

Don Juan Javier

Posted on 1 nov
• Updated on 4 nov

You (probably) don't need that useState +


useEffect
#react
#javascript
#codequality

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:

const MyAwesomeComponent = () => {

const [loading, setLoading] = useState(true);

const [data, setData] = useState();

// ---- PROBLEMATIC HOOKS: ----

const [items, setItems] = useState([]);

const [itemsLength, setItemsLength] = useState(0);

useEffect(() => {

someAsyncApiCall().then(res => {

setData(res.data);

setLoading(false);

});

}, [setData, setLoading]);

// ---- UNNECESSARY USAGE OF HOOKS: ----

// anytime data changes, update the items & the itemsLength

useEffect(() => {

setItems(data.items);
setItems(data.items);

setItemsLength(data.items.length || 0);

}, [data, setItems, setItemsLength]);

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.

This is actually pretty simple to pull off - here is one example:

const getItems = (data) => {

// I always like to protect against bad/unexpected data

if (!data || !data.items) return [];

return data.items;

};

const getItemsLength = (data) => {

return getItems(data).length;

};

Then, our component is simplified to the following:

const MyAwesomeComponent = () => {

const [loading, setLoading] = useState(true);

const [data, setData] = useState();

// DERIVED DATA - no need to keep track using state:

const items = getItems(data);

const itemsLength = getItemsLength(data);

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.

Banner Photo by Lautaro Andreani on Unsplash

Discussion (16)

João Lucas Silva • Nov 1


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

Don Juan Javier • Nov 2


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!

João Lucas Silva • Nov 2


João Lucas Silva • Nov 2

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

bro, the first code snippet has an error in it, I think:

useEffect(() => {

setItems(data.items);

setItems(data.items.length || 0);

}, [data, setItems, setItemsLength]);

Shoultd it not be this instead:

useEffect(() => {

setItems(data.items);

setItemsLength(data.items.length || 0);

}, [data, setItems, setItemsLength]);

Don Juan Javier • Nov 4


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]);

Don Juan Javier • Nov 4


• Edited on Nov 4


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

The following as you pointed out would cause an infinite loop:

// object

const user = {};

useEffect(() => { /* ... */ }, [user]);

const dogs = [];

useEffect(() => { /* ... */ }, [dogs]);

const fetchSomethingCool = () => {};

useEffect(() => { /* ... */ }, [fetchSomethingCool]);

Don Juan Javier • Nov 4


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...

John Sawiris • Nov 2


• Edited on Nov 2

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.

Don Juan Javier • Nov 3


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).

Spyros Argalias • Nov 2

Nice article, thanks

siva sandeep chintamaneni • Nov 1

You can use optional chaining in your conditional checks now, instead of multiple ||
conditions

Don Juan Javier • Nov 1


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...

Don Juan Javier • Nov 3


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

Don Juan Javier


Don Juan Javier

Coder, Digital Creator, Musician, Aspiring Game Dev

WORK
Senior Software Engineer

JOINED
30 oct 2021

Trending on DEV Community

3 Soft Skills To Succeed as a Developer


#programming
#productivity
#career
#todayilearned

Do you own a domain name without a purpose? Let me give you a project for it!
#watercooler
#discuss
#webdev
#startup

What's the best question you've been asked in a job interview?


#watercooler
#career
#discuss
#beginners

You might also like