Understanding State in React: Best Practices and Pitfalls

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

Previous LessonOptimizing Web Application Performance with Page Architecture in Next.jsNext LessonUnderstanding Local and Global State in Software Development

Lesson Transcript

  • [00:00 - 00:04] Hello, welcome to module two. In this module, we're gonna be talking about state.

  • [00:05 - 00:12] Specifically in this lesson, we're gonna get into what state is and some of the bad practices around state. So let's get going.

  • [00:13 - 00:29] So the first thing to understand is that state is essentially the data that represents a specific moment in time of our application. In particular, when you're talking about state in a React application, you can think of it as the memory of your components.

  • [00:30 - 00:43] It's gonna keep track of your user interactions, data inputs, anything that changes over time in the UI, it's gonna be kept stored within state. And really state is the key to reactivity.

  • [00:44 - 01:00] We tend to take for granted the behavior of React when it comes to automagically updating a piece of UI whenever you update a state variable. But truth is, there's a lot happening behind the curtain to generate that reactivity.

  • [01:01 - 01:14] You don't really have to think or code anything related to how that piece of your HTML gets updated. However, whenever you use your setter functions for the state, your component is gonna be updated.

  • [01:15 - 01:51] So what happens behind the curtain on a very oversimplified way is that when you call the setter function for your state variable, React will get that signal, understand that you want to update your component, and it will schedule a rerender of your component with a new value of the state variable. Unfortunately, some developers forget about that, or they just don't fully understand the importance of the setter function, and they just tend to skip it when trying to update the state by directly affecting the content of the state variable.

  • [01:52 - 01:59] However, when that happens, you really are skipping everything on the flow that I mentioned before. You're just updating a local variable.

  • [02:00 - 02:14] And simply because you're updating a local variable, it doesn't mean that your component has to be re-rendered. So you're skipping all the steps that I mentioned before, because of that, you're really not achieving that reactivity that you're looking for.

  • [02:15 - 02:24] Another bad practice when it comes to state management in React is thinking that the state updates that I mentioned are synchronous. Look at this diagram here.

  • [02:25 - 02:38] There are three steps at least, and this is an oversimplified version of it, between you calling the seter function and React re-rendering your component. So that doesn't really feel like a synchronous workflow.

  • [02:39 - 03:06] Essentially, this sample code here is very exaggerated, but it proves the point. If you were to write this example, and you can do it with the URL shown on the comment there, you will see that whenever you click the button, the value of the state variable "clicks", it's only updated by one, not by four, as you would expect, if you were to think of state updates as synchronous behavior.

  • [03:07 - 03:19] This happens because during the execution or during the lifecycle of your state at the current moment, the value of the clicks state variable is zero. That's the first execution of it.

  • [03:20 - 03:23] That's the first time your component is rendered. So it's a zero, which is the full value.

  • [03:24 - 03:40] And no matter how many times you try to update its value through the seter function, the value of the clicks local variable is always gonna be the same, zero. Whenever you call the seter function, you're essentially passing the zero value plus a one.

  • [03:41 - 03:58] It doesn't matter how many times you do it, it's always gonna be the same. The value of clicks is gonna change only once React finishes the lifecycle of all components, gets to yours, understands that there is a state update scheduled, performs it, and then re-renders your component.

  • [03:59 - 04:10] When your component is re-rendered, React is smart enough to understand that this is not the first time the component is being rendered. So it sets the new value to your state variable.

  • [04:11 - 04:23] And now on the second render of your component, the value of clicks happens to be one. And it's gonna be one again, throughout the entire lifecycle of your component until a new re-render happens.

  • [04:24 - 04:38] So keep that in mind, state updates are asynchronous, which means you can't really do what you're seeing here. Another really bad practice when it comes to local state is abusing the local state.

  • [04:39 - 04:51] Remember that we're saying that every time you update your state, you trigger a re-render of your component. So imagine having multiple instances of local state and triggering multiple updates on them.

  • [04:52 - 05:08] They would eventually trigger multiple re-renders of your component as well. And that's even worse if you share some of those stay variables with child components through props, because whenever that state variable changes, any of those child components is also gonna be re-rendered.

  • [05:09 - 05:29] So if you have multiple ones, then you can definitely generate a performance degradation by creating a bottleneck essentially of re-renders within your component. This actually happened to me while I was building an application that had on the left side of the screen, a very complex form, which allowed users to input many different types of data.

  • [05:30 - 05:45] And on the right, a set of generated images based on that data, because I was not properly thinking about the architecture of my page. And my components, I was linking the many, essentially local state variable for each input field.

  • [05:46 - 05:56] And several of those local state variables were also propped into the images components shown on the right side. At the beginning, as you can imagine, there were few images generated.

  • [05:57 - 06:06] And then, and so the application actually worked perfectly fine. But over time, I started noticing a degradation when the number of images increased.

  • [06:07 - 06:21] And that was because whenever I was typing something on one of those fields, every time those fields changed, the state variable was updated. And every time the state variable was updated, all my image components were also re-rendered.

  • [06:22 - 06:47] In the end, I ended up having a completely unusable UI, simply because I was inputting data or my input fields. So keep that in mind whenever you're creating a complex UI, try to isolate the state updates on single components and try to make sure that these components are not coupled through these state updates.

  • [06:48 - 07:09] Essentially, make sure that the interactivity is isolated and that each component works individually from each other. Another way to solve the problem I mentioned before, which involves an interdependency essentially of a parent component with a child component through a state variable, will be by doing something we call lifting state up.

  • [07:10 - 07:27] This is a very oversimplified version of what I was mentioning before, but here we have a component A that has a local state variable that is being propped into the component B. Whenever the local state variable of component A changes, a rerender is gonna be triggered and a rerender is also gonna be triggered to your component B.

  • [07:28 - 08:12] So we don't want that. So what we're doing here is if possible, and if the architecture of your page allows for since component A and B potentially have the same level, if you think on a hierarchy level of components, you could have the component, the state actually shared between them by uplifting the state into an ancestor component, essentially, whatever component is using them both, you can break that coupling between these components by separating them, simplifying them, but also making sure that they use the shared state individually, and they are not linked together and update on one cascades and update on the other one.

  • [08:13 - 08:25] So essentially in this video, we've covered state from many angles. We've addressed the common misconceptions about state that can potentially create bottlenecks and performance inefficiencies.

  • [08:26 - 08:46] So make sure to keep these lessons in mind whenever we talk about state in further videos, which is gonna be quite often, because they're gonna be the key to solving any of the other problems that we need to tackle. So next up on the next video, we'll continue to perfect our approach by introducing more sophisticated ways of sharing state between components.

  • [08:47 - 08:49] So until then, keep coding and see you in the next one.