In this post, we'll cover what the useRef
hook is, some examples of how it can be used, and when it shouldn't be used.
What is useRef?
The useRef
hook creates a reference object that holds a mutable value, stored in its current property. This value can be anything from a DOM element to a plain object. Unlike component state via say the useState hook, changes to a reference object via useRef
won't trigger a re-render of your component, improving performance.
Examples
Referencing a DOM element using the useRef Hook
In React, state manages data that can trigger re-renders. But what if you need a way to directly access document object model (DOM) elements that shouldn't cause re-renders? That's where the useRef hook comes in.
Typically, you'd do something like this.
import { useEffect, useRef } from "react";
export const SomeComponent = () => {
const firstNameInputRef = useRef<HTMLInputElement>(null);
// for plain JavaScript change the above line to
// const firstNameInputRef = useRef(null);
useEffect(() => {
firstNameInputRef.current?.focus();
}, []);
return (
<form>
<label>
First Name:
<input type="text" ref={firstNameInputRef}/>
</label>
</form>
);
}
- We create a variable named
firstNameInputRef
usinguseRef
to reference the DOM element (initially null) and useuseEffect
to focus the input element on the initial render. - Inside
useEffect
, we check iffirstNameInputRef.current
exists (it will be the actual DOM element after the initial render). If it does, we callfocus()
to set focus on the input.
Referencing a non-DOM element using the useRef Hook
Recently, I was working on Open Sauced's StarSearch, a Copilot for git history feature we released at the end of May 2024. You can read more about StarSearch in the blog post below.
We Made a Copilot Tool for you to Unlock the Power of Git History
BekahHW for OpenSauced ・ May 2
The ask was to be able to start a new StarSearch conversation. To do so, I had to stop the current conversation. If you've worked with the OpenAI API or similar APIs, they typically return a ReadableStream as a response.
A ReadableStream is a web API that allows data to be read in chunks as it becomes available, enabling efficient processing of large or real-time data sets. In the context of API responses, this means we can start handling the data immediately, without waiting for the entire response to complete.
I initially had this feature working, but ran into issues if the response started to stream. The solution, create a reference to the readable stream via the useRef
hook and when a new conversation is started, cancel the one in progress. You can see these changes in the pull request (PR) below
fix: now a new StarSearch chat can be started if one was in progress #3637
Now isRunning
is reset to false
when starting a new conversation. This was preventing the stream conversation from beginning when a previous one was cancelled and a new conversation started.
Fixes #3636
Before
After
- Go to any workspace and open StarSearch
- Start a conversation
- Cancel it by clicking the back button or new conversation buttons in the compact StarSearch header.
- Start the new conversation.
- Notice the new conversation streams in.
- [ ] Tier 1
- [ ] Tier 2
- [ ] Tier 3
- [x] Tier 4
So now, if someone presses the Create a New Conversation button, I cancel the current streaming response from StarSearch, e.g.
const streamRef = useRef<ReadableStreamDefaultReader<string>>();
// for plain JavaScript change the above line to
// const streamRef = useRef();
...
const onNewChat = () => {
streamRef.current?.cancel();
...
};
...
- We create a variable named
streamRef
usinguseRef
to hold a reference to the current ReadableStreamDefaultReader. - The
onNewChat
function checks ifstreamRef.current
exists (meaning a stream is ongoing). - If a stream exists, we call
cancel()
onstreamRef.current
to stop it before starting a new conversation.
Wrapping Up
useRef
was the perfect solution for my use case. Maybe you'll find the useRef
hook useful for something other than referencing a DOM element as well.
You can store almost anything in a reference object via the useRef
hook, and it won't cause re-renders in your component. If you're persisting component state, opt for useState
or other hooks like useReducer
so that the component does re-render.
For further reading on the useRef
hook, I highly recommend checking out the React documentation for the useRef hook.
Stay saucy peeps!
If you would like to know more about my work in open source, follow me on OpenSauced.
Top comments (17)
What are some other use cases you've used the
useRef
hook for?I once had some variables causing my hook to re-render infinitely. Fixed it by storing the variables using useRef
Besides accessing DOM elements, I've primarily used
useRef
for handling websockets (similar to your case), managing formData values, SVG drawing, and integrating third-party APIs that weren't designed for use with React.Nice! Thanks for sharing, Giovanni.
When you want track any kind of value and don't want to trigger rerender of components when that value is updated, that's the perfect use case of useRef.
In simple words when don't want to link the state to the UI.
Basically for any kind of value which should be be stored across but should not trigger a rerender.
Pretty much that. Thanks for giving it a read Red!
Have used it to replace useMemo and useCallback.
Great place to store an abort controller that can be accessed by several functions.
Yeah, that's another great example @link2twenty! Do have any example code lying around to share in a gist or CodeSandbox?
Here is a quick demo I just threw together. AbortController's are super good at preventing your frontend app spamming endpoints.
codesandbox.io/p/sandbox/abort-con...
Thanks for sharing!
Update 2024/07/20:
I updated the code sample below as @jgarplind pointed out to me on DMs over on Bluesky, that
firstNameInputRef.current
is not necessary in the dependency array and is misleading potentially to devs that you need it. Thanks, Joel!Informative
Glad you found it informative!
Interesting topic! Everything is explained articulately and clearly. For your project, consider checking out this free npm package: select-paginated.
Some comments have been hidden by the post's author - find out more