Interact and Update State in React with useState

Kent C. Dodds
InstructorKent C. Dodds
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 3 years ago

In this lesson, we'll create a Stopwatch component that will store the current time in its state. We will then have the DOM display the current time as the state is updated.

We'll cover:

  • React's useState hook
  • Setting a reference to an interval using React's useRef
  • Creating a handleRunClick function that handles flipping the running state plus creating and clearing an interval
  • Using the useEffect hook to clear the interval when the component unmounts

Instructor: [00:00] I have the static stopwatch that shows me the amount of time that has lapsed. I have a Start and a Create button. Let's go and make this dynamic. The first thing I'm going to do is I want to make the stopwatch component render some JSX that represents the state of the component.

[00:14] The state is going to be in this lapse amount of times, we'll say lapse and we'll initialize lapse to zero. Then, we also need running state. We can switch between Start and Stop, so we'll say running. If it's running, then it should say Stop, otherwise it should say Start.

[00:33] Then, we can say running is false. Of course, that's all wired up properly. Let's go ahead and get ourselves the useState hook, and lapse is now going to be useState. We'll initialize that to zero.

[00:48] That's going to return as an array with the first item is the state and the second item is an updater of the state, so that'll be setLapse. Then, on running we'll do the same thing. We'll say useState, we initialize that to false. Then, running will be the first item of that array that's returned and sub running will be the second item of that array.

[01:08] Of course, that's all wired up properly. We can initialize it to a 100 milliseconds, or we can initialize it to running is true, and we're all set there. Next, let's go ahead and make this interactive, so we can call these setLapse and setRunning update our functions as the user interacts with our component.

[01:25] On this Start and Stop button, I'm going to say onClick handle run click, and we'll make it that a function handleRunClick. Here, we'll simply say setRrunning not running. We'll toggle the running state, so I can click on Start and it turns to Stop, I can click on Stop and turns back the Start.

[01:43] Next, let's go ahead and start the stopwatch. Here I'll say, if it's running, then we'll do something, so actually need to stop it. Otherwise, we'll get our startTime which will be date.now minus the lapse.

[02:00] Then, we'll set interval. We'll setInterval to zero which means it will call this callback as quickly as possible it can. Then, we'll call setLapse date.now minus the startTime.

[02:14] If I hit Start, the stopwatch is going to go. If I hit Stop, it does not stop. Let's go ahead and make it stop. What we get back from setInterval is an intervalId. I need to store the intervalId, so that we can clear the interval.

[02:26] In this case, we'll call clearInterval. We need to pass it the intervalId. What we're going to do is we're going to pull and useRef here. I'm going to get my intervalRef. We'll just call useRef. We'll initialize it to No.

[02:43] Then, when we call setInterval, we'll just say intervalRef.current equals setInterval. Then, when we click that stop button, we can say clearInterval intervalRef.current.

[02:56] Now, I can click Start and Stop, Start and Stop. If I hit Start and Clear that's not working. Let's wire that up. Here down in my button, I'll say onClick handle clear click. We'll make that a function called handleClearClick. It will take no parameters.

[03:15] We'll simply clearInterval, intervalRef.current. We'll set the lapse to zero and setRunning to false. Great. We had Start, Stop, Clear, and Start, and Clear. One last thing that we should do to optimize this component is, if we hit the Start button, and this setIntervals being called as frequently as it possibly can.

[03:40] Then, the component is unmounted. This setIntervals cannot continue to go. We need to make sure that we clear the interval when this component is unmounted. What we're going to do is we're going to use effect, pull that useEffect, hook in here, and we'll useEffect.

[03:56] We actually don't need to do anything when this component is mounted. We just need to do something when the component is unmounted. I'm going to pass an empty array as the second argument. Make sure that this callback function is only run one time and that is returned, cleanup function is also only run one time when the component is unmounted.

[04:17] I'm going to call clearInterval intervalRef.current, and that should get us all set to be able to Start, Stop, Clear, and have a Start, and have a unmount and clear the interval when it's unmounted.

[04:31] In review to write out the stopwatch, we first took this JSX, made it represent the state of our component. Then, we created the state of our component using useState. We wired up our buttons to these functions that took care of the logic for the stopwatch component.

[04:46] We have to keep track of the intervalId. We created an intervalRef that we use to manually set the current value to this setInterval ID. We were able to clear that interval if the user hit Stop, or if they hit Clear, or if the component is unmounted using the useEffect hook with an empty array and returning a cleanup function.

Vadym Kalinin
Vadym Kalinin
~ 5 years ago

Thanks for the great overview of new stuff! If I understood correctly, using normal variable for intervalRef instead of react reference, wouldn't change anything. But then, why did you go with react ref for it?

Kent C. Dodds
Kent C. Doddsinstructor
~ 5 years ago

Not quite. The fact that it "works" when you try that is actually a side-effect of the fact that it's happening so quickly. Dig a little deeper and you'll see that it doesn't actually do what you think it's doing if you don't use the ref.

Krasimir
Krasimir
~ 5 years ago

I'm struggling to see the difference between useRef and useState in the context of this example. Are they interchangeable for keeping the interval ref?

Oroneki
Oroneki
~ 5 years ago

Great to know that we can use refs to something other than DOM nodes. So many info in just 5 minutes!

Kent C. Dodds
Kent C. Doddsinstructor
~ 5 years ago

Krasimir, we could actually use useState for the intervalId, and then call the updater (setIntervalId) instead of setting intervalId.current. The difference is that with useState, using the updater will trigger a re-render. In our case we don't need a re-render when the interval id is set so it's better to use useRef.

Krasimir
Krasimir
~ 5 years ago

Ah right. It makes sense. Thanks for the clarification.

Kalin Chernev
Kalin Chernev
~ 5 years ago

It's no doubt a useful tip to use useRef instead of a variable - thanks! Is it something related to how the browser works with clearInterval or rather React's hook internals specifics that it doesn't work as expected with a variable rather than a value of useRef?

Kent C. Dodds
Kent C. Doddsinstructor
~ 5 years ago

It's more just about how JavaScript works. Every time the component is rendered, that function is called again. We need to have some mechanism to keep track of that value between renders and that's what refs are intended for.

Kalin Chernev
Kalin Chernev
~ 5 years ago

Thanks!

vassiliskrikonis
vassiliskrikonis
~ 5 years ago

How about a variable outside the component function?

Kent C. Dodds
Kent C. Doddsinstructor
~ 5 years ago

That would work, but what if I want to render more than one in my app?

Gaurav K
Gaurav K
~ 5 years ago

Thanks for explaining useRef in this example.

Here is the official documentation around this usage. https://reactjs.org/docs/hooks-reference.html#useref https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

Quang Le
Quang Le
~ 5 years ago

Thanks Gaurav for pointing out the detail documents about useRef. I can see some related phrases like

  • "useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.", and
  • "useRef will give you the same ref object on every render.", I still prefer the clearer and more practical usage in this "some mechanism to keep track of that value between renders and that's what refs are intended for" from Kent. It is an excellent video!
~ 2 years ago

Looks like the code is no longer at sandbox...

Markdown supported.
Become a member to join the discussionEnroll Today