Add State with the useState Hook to a React Function Component

Elijah Manor
InstructorElijah Manor

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 5 years ago

Historically, you had to use a React Class Component in order to maintain state in a component. However, with Hooks, you can now add state to a Function Component using the useState hook. In this lesson, we will explain how to use this API and then convert a portion of an existing Class Component to a Function Component.

Instructor: [00:00] Here, I have a simple app bootstrapped by Create React App. Let's kick up the dev server with npm start.

[00:06] On the right, the small app uses state to mirror text entered in the textbox on the screen, as well as mirroring the state of the checkbox as you toggle it. Let's cancel our dev server and check out the code.

[00:19] Navigating to the playground.js file, you can see that we have a React class component. Historically, to maintain state, you had to use a React class component, but with hooks, you could add state to a function component. Let's do that.

[00:33] We'll first swap out the class with a function, but now, we need to figure out what to do with the constructor. For now, let's just remove it completely and remove the render method, since whatever's returned from our function is the render.

[00:46] Now, we need to address the state we just removed. React provides several hooks. One of those is the useState hook, which is what we'll use to maintain state. We'll start by focusing on the state for the textbox.

[00:59] The way it works is that you pass the initial state as an argument. In our case, we'll start with an empty string. The useState hook returns an array. We'll call it state, for now.

[01:10] The first entry of the array is the current value of whatever you put into state. At this point, it's the empty string, since we haven't updated it, yet. The second entry of the array is a function to update the state.

[01:22] In both cases, we could have called text and setText to whatever we wanted, because they're just variables. Line 11 could stay the same since our variable is the same, but instead of calling setState, we'll call our setText updater. It accepts only the state for the text.

[01:40] Once setText is invoked with a new value -- like "Hello," for example -- the next time useState on line 4 gets called, it will return the updated text of "Hello," not the initial empty string. It remembers.

[01:55] In order to support the checkbox's state, we could come up here and duplicate these lines, but there's another way to clean this up. We could combine these three lines into one by using JavaScript array destructuring. We'll place the same variable names that we had before.

[02:12] This makes the code much more terse and keeps us from having to introduce an intermediate variable. Let's copy and maintain state for the checkbox. We'll call our state checked and our updater function setChecked. For our default value, we'll set it to false.

[02:29] As before, we'll need to update the onChange handler to use the new updater function instead of this.setState, and pass it only the Boolean that's changing.

[02:40] At this point, all should be well. Let's go over, and kick up our dev server again, and test it out.

[02:47] Here on the right is our updated hooks version of the app. Sure enough, it seems to work just fine. Both the text and the checkbox states are being reflected on the UI.

[02:59] Much like you could with class components, you can move your event handlers around. For example, we'll create a handleCheckboxToggle function and use the same logic that we had before. It works like it did previously.

[03:12] If we wanted to, since this is a toggle, we already have the state. We could just swap out the argument with not checked instead. That' works, too.

[03:23] Another option is to provide a function to the updater. This is similar to setState's function API if you've used it. It'll pass the previous state to the function, and you can return what you want the new state to be.

[03:36] In our case, we'll take the previous checkState and return the flipState. Again, that works, too.

[03:43] You might be wondering if useState only understands primitive values like strings and numbers. The answer is no. Let's take a look at using an object instead, which you're probably much more familiar with since that's what React class state uses.

[03:58] Let's combine our state. We'll call our updater setState. Our initial value will be text of empty string and checked of false. For our handleCheckboxToggle we'll call setState instead, and like we would in a React class, we'll only pass the part that changed, the checked property.

[04:17] For our values, we'll take the properties of the state object, and then as we did for the checkbox handler, we'll change out the setTextUpdater with setState, and pass the new text value. Lastly, we'll update the values being mirrored to be accessed off the state object.

[04:35] Now if we test it, oh, wait. It broke when trying to access the checked property. It turns out that the useState hook's updater does not do a shallow merge like setState does in the class component. When we went to set the text value, it wiped out the checked state that we had before.

[04:56] If we wanted to use the same approach, we could introduce a new updater method that does the merge for us. We'll call it mergeState, accept a partial state, merge it with the previous state, and return that. For simplicity, we'll just remove the handleCheckboxToggle function and inline both of them.

[05:18] We'll replace setState with our new merged state, and do the same for the checkbox's onChange, passing only the checked prop. Now, if we test again, it'll work just fine, because we implemented a shallow merge similar to what you'd see in a class component setState.

[05:36] In general, the React team recommends you to split state into multiple state variables depending on which values tend to change together. It's up to you on how you split apart your state.

[05:47] Now that we've updated a simple app, let's switch to a slightly more complex app and apply state via the useState hook. We'll leverage the to-do app shown on the right. You could add to-dos, check them off, and delete them.

[06:05] We'll bring up the to-do list class component and take a look at it before converting it to a function component with hooks. In the render method, we have a new to-do component and a list of to-do item components.

[06:17] To convert, we'll first replace the class with a function. We'll need to pull useState from our React import.

[06:26] Then we'll create state for the new to-do, and call the updater updateNewTodo. We'll set an empty string as the initial value.

[06:37] Then we'll create more state for the to-do's array, call its updater updateTodos, and initialize it with an empty array. Now, we could remove our constructor.

[06:49] For our class methods, we'll convert those to local functions. For handleNewChange, we'll replace this.setState with updateNewTodo, and only pass the string portion to be updated.

[07:03] For handleNewSubmit, we'll replace this.setState with the updateTodo's updater, accept the previous to-do state, and return a new array with a new to-do appended to it. For this case, we'll update the new to-do to an empty string.

[07:23] Likewise, we'll need to update handleDelete to use the updateTodo's updater using the previous to-do state, and filtering out the item to be deleted. Finally, updating the handleCompleted toggle to updateTodos, and tweak the completed value inside the to-do that was clicked on.

[07:46] At this point, we could remove the render method and replace instances of this dot with nothing, since they are now local function variables. That should be it.

[07:57] If we come back over to our app, it should still work, and it does. Yay, hooks.

Adam
Adam
~ 5 years ago

Is there a missing video? How did it go from Environment Setup to this? Even the CodeSandbox doesn't match (it actually doesn't even work).

Fergus Meiklejohn
Fergus Meiklejohn
~ 5 years ago

It might not be working because the versions of React and ReactDOM aren't correct in the CodeSandbox. Now they need to be ^16.8.0-alpha.1

Hyun Wook Kim
Hyun Wook Kim
~ 5 years ago

Wouldn't it have impact on the performance given that handleNewChange and handleNewSubmit functions are going to create new functions on every render or is react smart enough to somehow optimize that?

Hyun Wook Kim
Hyun Wook Kim
~ 5 years ago

Wouldn't it have impact on the performance given that handleNewChange and handleNewSubmit functions are going to create new functions on every render or is react smart enough to somehow optimize that?

Let me rephrase that first bit. I meant that new functions will be created and assigned to handleNewChange and handleNewSubmit, and I'm worried about how this is going to impact performance. My question is, is react smart enough to optimize that or should we structure the code differently to achieve better performance?

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Adam, there is no video between the setup and this lesson. I wanted the focus of the lessons to be on hooks and not building class based components. However, I can see how that could be a bit jarring. At the point you watched the lesson there was a new version of the prerelease which caused this one to break. Everything has been updated so it should work fine now. Sorry for the confusion.

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Adam, there is no video between the setup and this lesson. I wanted the focus of the lessons to be on hooks and not building class based components. However, I can see how that could be a bit jarring. At the point you watched the lesson there was a new version of the prerelease which caused this one to break. Everything has been updated so it should work fine now. Sorry for the confusion.

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Fergus, yes... all deps have been updated now. Thanks

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Hyun,

For the most part you shouldn't need to consider new functions as a bottleneck to performance. If you find you app getting slow, then yes, I recommend going deeper but I tend not to optimize too soon as it may have little or no perceivable impact on the user since React does a pretty good job at perf out-of-the-box. However, when you do get into situations where you need to optimize then using useCallback, useMemo, memo, and useReducer can help in those situations https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render

Vansh Gambhir
Vansh Gambhir
~ 5 years ago

For files Playground{1-5}.js, is there any reason for not exporting the function as default. It took me a while to realize why I couldn't call them after importing into App.js

Michael Friedman
Michael Friedman
~ 5 years ago
Michael Friedman
Michael Friedman
~ 5 years ago
Jesús Mendoza
Jesús Mendoza
~ 5 years ago

In the React documentation it says you can't call hooks from nested functions and you are calling the setState hooks inside the handleNewChange and handleNewSubmit. Is that ok?

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

In the React documentation it says you can't call hooks from nested functions and you are calling the setState hooks inside the handleNewChange and handleNewSubmit. Is that ok?

Jesús, the hooks rule you are referring to https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level applies to calling the hook (useState, useEffect, etc...) not calling a function that one of them returns. setState is being returned from a useState hook and called later, which is okay. Good question.

Jesús Mendoza
Jesús Mendoza
~ 5 years ago

In the React documentation it says you can't call hooks from nested functions and you are calling the setState hooks inside the handleNewChange and handleNewSubmit. Is that ok?

Jesús, the hooks rule you are referring to https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level applies to calling the hook (useState, useEffect, etc...) not calling a function that one of them returns. setState is being returned from a useState hook and called later, which is okay. Good question.

Oh, ok. Thank you for the clarification and the fast response. It didn't make sense to me not to be able to call setState from inside a function. Thanks!

Shaun
Shaun
~ 4 years ago

For those confused as to why the codesandbox code doesn't match the video code if you uncomment the Playground import and set it as the default export you'll be good to go

peter
peter
~ 4 years ago

Where does prevState come from? is that something that is just defined inside of a hook?

Naman Sancheti
Naman Sancheti
~ 4 years ago

For those confused as to why the codesandbox code doesn't match the video code if you uncomment the Playground import and set it as the default export you'll be good to go.

Specifically, you need to add the 'default' keyword to any of the 'Playground' functions in the 'TodoList.func.js' file to make it the default export.