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.