Use the React useRef Hook to Reference a DOM Element or Simulate an Instance Variable

Elijah Manor
InstructorElijah Manor

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 5 years ago

Refs have been a handy way to reference DOM elements in Class Components, but did you know you can use refs for other things as well? In this lesson, we’ll examine how to leverage the useRef hook inside a Function Component. We’ll first use this ref to attach to a React element, which you may already be familiar with, but then we’ll examine how to use a ref to simulate a Class instance variable.

Instructor: [00:00] We'll start a dev server to run a simple react app that you can see on the right. The app is a react class component and uses a react.create ref so that the focus button can access the input dom element, set focus, and select the text.

[00:16] Let's take a look at the code for this and show how you could do the same thing with the react function component and hooks.

[00:23] The playground component has the code for the app we just saw. You can see the create ref in our constructor. We assign that to the ref prop of our input component. On the on click of the button, it calls the focus and slight methods off the dom element.

[00:40] In order to convert this class to a function component, we'll need to import use ref from react and then replace class with function. Next, we could remove the constructor and we'll introduce a local input ref variable and set it to the return of the use ref hook. Then, we'll convert the handle click method to a local function and remove the this.references in our file with nothing.

[01:07] Finally, we'll remove the render method and just return what we want to be rendered. It should work just as it did before, and it does. Nice.

[01:18] Refs can actually be used in another way. They weren't only made for dom elements. You can actually use refs to simulate instance variables like you would use in a class. Let's switch up our app to use a todo list, instead. Let's explore this alternate technique.

[01:36] The class version of our todo list app keeps track of the index values of each todo and increments them. If you refresh and create a new one, it's smart enough to take the highest value from before and increment that one.

[01:52] It starts at zero and when it reads from local storage, it updates to the maximum number that it finds. Then, on additions, it just increments from there.

[02:02] If we switch to the function component version of this todo list, we'll find that it takes the easy way out and just uses date.now, which is the number of milliseconds since January 1st of 1970. If we run the app and add an item, you'll see our index is huge. Let's go and use a ref hook inside our function component to simulate what the class component was doing.

[02:27] First, we'll import the use ref hook from react. Then, we'll create a todo ID variable and assign it to the return of use ref. We'll initialize the ref to a value of zero.

[02:41] Let's go grab the logic from the class version to use in our new function component.

[02:47] Inside our initial todo's function, we'll expand our one liner function, save off our parsed storage and a value from storage variable, and make sure we return that from our function. However, right before, we'll paste the code from our class component, delete the this.part, make sure we access the current property off the ref, and replace todos with value from storage.

[03:11] That'll reduce over all the entries, looking for the maximum ID value.

[03:17] The last step is to increment our todo ID when creating a new one and to use that value instead of date.now. Now, we should be able to test our app again, but this time our ID should be the next in line. It is. It's five.

Alex Ung
Alex Ung
~ 5 years ago

Thanks for the videos. If the todoId can be kept track of using either useState or useRef, what are the pros and cons to each approach?

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Alex, good question about where to keep todoId. You could technically do either as you mentioned, but I lean toward keeping data that is needed for the render inside of useState. In this case the todoId isn't needed for the render, but is intended to keep track of the last ID so it can be incremented when a new item is added. Technically that variable could be stored anywhere (even global, but I prefer to limit global variables).

Tony Catalfo
Tony Catalfo
~ 5 years ago

This course is wonderful! Putting everything on codesandbox helps tremendously. I was wondering what the reason was to have this line of code inputRef.current.focus(); line 30 of the focus example. Don't we get the same result if we just use line 31? inputRef.current.select();

Thanks Anthony

Tony Catalfo
Tony Catalfo
~ 5 years ago

What is the advantage of useRef over something like this: let count = Number(window.localStorage.getItem('count') || 0) let incCount =()=>{ window.localStorage.setItem('count', count + 1) return count+1 }
.... // todoId.current id: incCount() , text: newTodo, completed: false

Tony Catalfo
Tony Catalfo
~ 5 years ago

id: incCount() , text: newTodo, completed: false

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Tony, good question. MDN notes that...

Calling element.select() will not necessarily focus the input, so it is often used with HTMLElement.focus().

https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select

Elijah Manor
Elijah Manorinstructor
~ 5 years ago

Tony, another good question. It'd be better if the UI didn't figure out the IDs in the first place, but since there is no server counterpart the UI is doing the logic. You could introduce another localStorage item that you always keep up to date with the largest ID known, however, if that number was based on count it could lead to bugs. Imagine you had 10 items and deleted 5 of them. If it was based on count, then the next item you create could reuse an existing ID (depending on what you deleted). So, that is why when the app loads I iterate through the todos and look for the Max ID and set that to the ref. You could also store that to localStorage, but you'd need to keep it up-to-date and make sure it kept track of the largest ID that exists. If you had TONS of data in localStorage then my solution may be slower on startup since I do a loop, but browsers are pretty fast so I don't think that's a worry.

Kostiantyn Hryshyn
Kostiantyn Hryshyn
~ 5 years ago

why not just write let todoId = 0; and then just change todoId++ value? =)

Jaime Vega
Jaime Vega
~ 5 years ago

why not just write let todoId = 0; and then just change todoId++ value? =)

Because the values of local variables inside the function are gone after each render. So according to your suggestion, todoId will always be zero. When putting the values in state of refs, the values it contains are persisted between renders.

Alexander Myshov
Alexander Myshov
~ 4 years ago

why not just write let todoId = 0; and then just change todoId++ value? =)

Creating variable outside of the function can do the trick and retain value between rerenders but there is a small issue. Every instance of component in the application will share the same value.