loading...
Cover image for 3 React Mistakes Junior Developers Make With Component State

3 React Mistakes Junior Developers Make With Component State

thawkin3 profile image Tyler Hawkins Updated on ・4 min read

One of my favorite things about web development is that there's always something new to learn. You could spend your whole life mastering various programming languages, libraries, and frameworks and still not know it all.

Because we're all learning, it also means we're all prone to making errors as well. This is ok. The goal is to get better and to be better. If you make a mistake and learn from it, you're doing great! But if you fail to learn anything new and continue to make the same mistakes repeatedly, well... then it sounds like you may be stagnating in your career.

In that spirit, here are three common mistakes I often see during code reviews that junior developers make when dealing with React component state. We'll take a look at each mistake and then discuss how to fix it.


1. Modifying state directly

When changing a component's state, it's important that you return a new copy of the state with modifications, not modify the current state directly. If you incorrectly modify a component's state, React's diffing algorithm won't catch the change, and your component won't update properly. Let's look at an example.

Say that you have some state that looks like this:

this.state = {
  colors: ['red', 'green', 'blue']
}

And now you want to add the color "yellow" to this array. It may be tempting to do this:

this.state.colors.push('yellow')

Or even this:

this.state.colors = [...this.state.colors, 'yellow']

But both of those approaches are incorrect! When updating state in a class component, you always need to use the setState method, and you should always be careful not to mutate objects. Here's the right way to add the element to the array:

this.setState(prevState => ({ colors: [...prevState.colors, 'yellow'] }))

And this leads us right into mistake number two.


2. Setting state that relies on the previous state without using a function

There are two ways to use the setState method. The first way is to provide an object as an argument. The second way is to provide a function as an argument. So, when would you want to use one over the other?

If you were to have, for example, a button that can be enabled or disabled, you might have a piece of state called isDisabled which holds a boolean value. If you wanted to toggle the button from enabled to disabled, it might be tempting to write something like this, using an object as the argument:

this.setState({ isDisabled: !this.state.isDisabled })

So, what's wrong with this? The problem lies in the fact that React state updates can be batched, meaning that multiple state updates can occur in a single update cycle. If your updates were to be batched, and you had multiple updates to the enabled/disabled state, the end result may not be what you expect.

A more correct way to update the state here would be to provide a function of the previous state as the argument:

this.setState(prevState => ({ isDisabled: !prevState.isDisabled }))

Now, even if your state updates are batched and multiple updates to the enabled/disabled state are made together, each update will rely on the correct previous state so that you always end up with the result you'd expect.

The same is true for something like incrementing a counter.

Don't do this:

this.setState({ counterValue: this.state.counterValue + 1 })

Do this:

this.setState(prevState => ({ counterValue: prevState.counterValue + 1 }))

The key here is that if your new state relies on the value of the old state, you should always use a function as the argument. If you are setting a value that does not rely on the value of the old state, then you can use an object as the argument.


3. Forgetting that setState is asynchronous

Finally, it's important to remember that setState is an asynchronous method. As an example, let's imagine that we have a component with state that looks like this:

this.state = { name: 'John' }

And then we have a method that updates the state and then logs the state to the console:

this.setState({ name: 'Matt' })
console.log(this.state.name)

You may think that this would log 'Matt' to the console, but it doesn't! It logs 'John'!

The reason for this is that, again, setState is asynchronous. That means it's going to kick off the state update when it gets to the line that calls setState, but the code below it will continue to execute since asynchronous code is non-blocking.

If you have code that you need to run after the state is updated, React allows you to provide a callback function that gets run once the update is complete.

A correct way to log the current state after the update would be:

this.setState({ name: 'Matt' }, () => console.log(this.state.name))

Much better! Now it correctly logs 'Matt' as expected.


Conclusion

There you have it! Three common mistakes and how to fix them. Remember, it's ok to make mistakes. You're learning. I'm learning. We're all learning. Let's continue to learn and get better together.

(Bonus points if you understood the cover image's reference.)


Edit: I was frequently asked if the same principles I've outlined in this article also apply to function components and hooks. I decided to write a followup article that focuses on exactly that! You can find it here:

https://dev.to/thawkin3/3-mistakes-junior-developers-make-with-react-function-component-state-88a

Posted on by:

thawkin3 profile

Tyler Hawkins

@thawkin3

Senior software engineer. Continuous learner. Educator. http://tylerhawkins.info

Discussion

markdown guide
 

I'm completely new to React so thank you for the explanations! And how to do things properly. But I have a question if you would be so kind to reply: #2 tells us to use functions, but #3 uses an object { name: 'Matt' }. So is it just for example or it's ok to sometimes use objects instead of functions?

 

Good question! I should clarify my article to help explain that better.

You should use a function of the previous state when you are updating the state that relies on the previous state. So like in the two examples I gave, enabling/disabling a button relies on the previous state of whether or not is was disabled. And incrementing a counter relies on the previous value of the counter.

If you aren't relying on the previous state but are just setting an entirely new value, then using an object as an argument is perfectly fine. For example, if you were fetching a list of users from the server and then needed to store that list of users in the component's state, it would be perfectly fine to do something like:

this.setState({ users: fetchedUserDataHere })

Because in that case it doesn't matter what the previous value of users was before. You just want to store the new data you just fetched.

Does that help?

 

As an update, I updated the article just now to hopefully be more clear.

I changed the header text to:

Setting state that relies on the previous state without using a function

And I added this paragraph at the end of the section:

The key here is that if your new state relies on the value of the old state, you should always use a function as the argument. If you are setting a value that does not rely on the value of the old state, then you can use an object as the argument.

 

Yes, I think I get it now. Thank you one more time! 😄

 

use functions when you rely on previous state values, like toggle button, increment counter, etc. It's okay to use objects when the new state is independent of previous (like in #3). Correct me I'm wrong.

 

Yep! That's exactly right.

 

#3 is so accurate. I made this mistake countless times as a Junior. Even now with React hooks I'll catch myself trying to do something synchronously and go "oh right, can't do it like that" and end up hooking it up through an effect.

Edit: Add escape characters.

 

Good post! I hope TypeScript flavoured react would provide decorators or generics to prevent misuing the state in the first place. E.g. setting the properties read-only and make forcing the use of prevState more obvious. In this way Angular is a little bit better, as it has more structure around this that makes harder to make human mistakes.

 

"The key here is that if your new state relies on the value of the old state, you should always use a function as the argument."

I knew this already but your example really helped clarify it. Thank you for sharing! As a React newbie this kind of post is super useful to me to solidify what I'm learning.

 

You’re welcome! I’m glad I could help. Keep on learning and being awesome!

 

Good write up!

Please do not assume only junior developers make these kinds of mistakes :).

 

Haha very true! Even those with more experience have made these mistakes. Maybe what I meant is "people new to React" often make these mistakes.

 

3 - This mistake is the most frustrating one until you find out the reason. React made me think async in everyday life too😅

 

I've been working with React for nearly two years now and this helped me so much! I've never broken through on when to use prevState with setState your example really helped click. Now I've got some button states to go fix :)

 
 

Thank you for the good examples and explanation on how to avoid such mistakes!

 
 
 
 

I'm currently learning in Udemy's React Course by Stephen Grider and this post is really helpful for me. Thank you!

 
 

Mistake Junior React Devs make is using component classes instead of hooks and useState

 

Just for you, I wrote a similar article now, but this time with function components and the useState hook. ;)

dev.to/thawkin3/3-mistakes-junior-...

 

Ehh... sort of. Hooks are cool and solve a lot of problems and frustrations people have had with class components, but class components are here to stay, at least for now. It's very likely that any developer working on a codebase that existed before hooks were released will be dealing with class components, so it's still important to understand how state works with class components.

 
 
 

Thank you so much, i was struggling with the last problem. I did not know that setState is asynchronous

 

You’re welcome! That one trips up a lot of people.

 

Later two were good information. Although I knew, but thanks for reminding me

 

Ah yes I did that when I started too

 

Thank you Tyler for this very helpful article. #3 had me questioning my programming knowledge at some point before I understood that setState is asynchronous