Every Beginner React Developer Makes This Mistake With State

preview_player
Показать описание

By far one of the biggest beginner React mistakes I see is developers storing derived state. This is a huge problem that is incredibly hard to detect unless you know what you are looking for. In this video I will show you what the signs are of derived state so you can make sure you never store derived state.

📚 Materials/References:

🌎 Find Me Here:

⏱️ Timestamps:

00:00 - Introduction
00:48 - Derived state problem
02:45 - How to fix the problem
04:26 - Performance considerations

#React #WDS #useState
Рекомендации по теме
Комментарии
Автор

I wouldn't call this example a "derived state example". This is two different states having a relationship. Derived state means a value that is directly calculatable from other states. Selected user cannot be calculated from other states (users), it simply requires a user input ( outside event). This is still a good example of "states with relationship, should be updated together" example.

cmlttnts
Автор

my app works just fine coz I put the selectedID in the dependency array of the useEffect where I fetch API and set the state.... but now I see how silly my solution is while I can just use your simple one line of code to define selected it immediately! thank you!!!

jinggong
Автор

I think derived state doesn't need to use the useState at all, it should be calculated based on users state.
So I'm not sure is this a truely example of derived state.

yanhuan
Автор

If you have a list of items that doesn't change as often, you could simply save the index of the item in your array. So you don't have to loop through the array to find the correct item

UndefGned
Автор

You actually "break" the reference to that object when you do return { ...user, etc }, this creates a new object with the same contents, therefore: new reference. You should apply the increment directly to user and return user to keep it working straight.
But if you work with references you open up to a lot of problems if you dont really comprehend how references work. Unless you dominate or are trying to dominate references, use native values to avoid issues.

wallghing
Автор

Thanks. This is a pretty common but annoying possible bug. This is another reason why I love svelte so much, just use $: and it becomes reactive, derived or whatever you want.

krishgarg
Автор

3:02 — Try to press `F2` for renaming const in whole document. It really helpful sometimes.

baileysli
Автор

Thanks so much 🙏 I just removed unnecessary code and increase performance in my project

godwinjohn
Автор

well, good solution but this solution as you said had bad performance since you need to loop in the users' array to find the selected user, 1)alternative solution is to store the users in an object instead of an array like this:

const [users, setUsers] = useState({
1: {
name: "keyle",
age: 27,
},
2: {
name: "sally",
age: 30,
},
3: {
name: "mike",
age: 25,
},
});
and now the selected state can be any id (1, 2, 3) and you can access the selected user object with o(1)
and you can make an array of users id to keep track of their sequence like this:
const sequence = [2 , 1, 3]

2)another solution is to keep the users state an array of obj like you did but instead of saving the user id in the selected state, we can just save it is index (since the order does not change)


thank you for the video amazing <3

mohamadmohamad
Автор

what he didn't explain is WHY "selectedUser" and the user that its age is incremented are not the same reference?
the reason is that when incrementing one user's age we mutate that user it by destructuring .. so we're basically creating a new one : return { ...user, age: user.age + 1 }

akrembc
Автор

Instead of useMemo maybe put the users in a Map using the ID as the key, or maybe save the array index of the selected user in the state rather than the ID.

Mickey_McD
Автор

Why did you pass reference type variable as a dependency to use Memo
It will change on every render and useMemo will just become inefficient

karansamra
Автор

Now I wonder if I ever did this. It seems like a obvious mistake, but of course it can get you in some weird troubles.

lucappelman
Автор

Just copy my reply to comment so new devs might be aware of. There are few problems with the video example and solution.

For the example itself, check the top comments with highest vote regarding the definition of derived state.

I want to point out the problems with that solutions from some of the comments and the video, the video example is really tricky.

First, some of comments say use array index so you don't need to use array.find(), it's questionable/wrong to use array index id as selectedUserId cuz the array index will cause bug once the array size changed hence index is not longer unique. If u pretty sure the size and position are static under any circumstances. This approach is probably the most performant and simplest solution

Second, use the unique user id as the find id. This is the video solution, it's good but when the size is large, it will cause performance issue as the video claimed later.

However, the useMemo() in this case, doesn't solve the performance issue because when you update the selection, it will modify the array hence cause useMemo() to rerun. the useMemo will be useful only when the parent container is changed but at the same time you don't want to re-render the current selection list. But for this example with its context, the useMemo() solution is simply wrong. the trouble array.find() will still run regarless.

So what's my answer?
I would say if the list is large, the performance is important, the initial code is fine.
You just need to update both selectedUser & users when you call the update, and only update selectedUser if the user id is matching the updated user in users list. You don't even need to use the array.find() and useMemo() in the first place, it also reduces the mental overhead of using memo hooks.

The root issue of his solution is because the "issue" example he provided doesn't match the definition of derived state. It just looks like it, that's why I called it tricky.
If the example is derived state, you shouldn't need useMemo() most likely.

doc
Автор

Oh wow I thought after a week or 2 of learning react, I got the hang if it but man did I find this video hard to keep track of

bernardus
Автор

When he uses the "users" array to keep track of changes with the useMemo() call....
1. What is react checking exactly behind the scenes? Just the pointer to the array "users" or a more deep comparison (things like length, changing the objects (per se or sub-properties) inside each index, etc) ?
2. Also isn't "users" being re-created on each render cycle? Wouldn't useMemo going to be doing the find() operation every single time?

gerasTheMessiah
Автор

Great lesson!
This is a source of many bugs... I've tried solving it in another way (as a practice).
My goal was to fix the bug and improve performance, and this is what I came up with.

export default function UserLop() {
const [users, setUsers] = useState([
{ id: 1, name: "Kyle", age: 27 },
{ id: 2, name: "Sally", age: 32 },
{ id: 3, name: "Mike", age: 54 },
{ id: 4, name: "Jim", age: 16 },
]);
const [selectedUserIndex, setSelectedUserIndex] = useState();

function incrementUserAge(id) {
setUsers((currUsers) => {
return currUsers.map((user) => {
if (user.id === id) {
return { ...user, age: user.age + 1 };
}
return user;
});
});
}

return (
<div>
<h3>
Select User:{" "}
{selectedUserIndex == null
? "None"
: is years old`}
</h3>

{users.map((user, index) => {
return (
<div key={user.id}>
{user.name} is {user.age} years old.{" "}
<button
onClick={() => {
incrementUserAge(user.id);
}}
>
Happy birthday
</button>{" "}
<button
onClick={() => {
setSelectedUserIndex(index);
}}
>
Select
</button>
</div>
);
})}
</div>
);
}

What do you think?

afabio
Автор

If we can store the array position in selectedUser state and on update of user in useEffect we can use this array position like user[selectedUser] then it will be far easier and faster compilation of code

rahulnag
Автор

To go a step further into performance, I'd keep the users state as an object instead, where key is the id and value is the user, so your derived selectedUser state wouldn't have to loop (find) throught the users array every time selectedUserId changed, but actually access it directly e.g. const selectedUser = useMemo(() => users[selectedUserId], [selectedUserId, users]);

ioannispotouridis
Автор

I think states having relationship with each other should be treated with useReducer.. as you need an single state but multiple ways to update it...

iam_ck