Refs and useRef hook
After creating a ref object using the useRef hook, you can set it as the value of a ref attribute of an element you want to reference.
When you want a component to “remember” some information, but you don't want that information to trigger new renders, you can use a ref.
Perhaps the best way to describe a ref is as a bridge; a bridge that allows a component to access or modify an element the ref is attached to. Using refs gives us a way to access elements while bypassing state updates and re-renders; this can be useful in some use cases, but should not be used as an alternative to props and state as stated in the official React docs.
Refs are escape hatches for React developers, and we should try to avoid using them if possible.
When to use refs
Typically, you will use a ref when your component needs to “step outside” React and communicate with external APIs - often a browser API that won't impact the appearance of the component. They are used for references in React, to store a reference to particular DOM nodes or React elements, to access React DOM nodes or React elements, to interact with React DOM nodes or React elements, and to change the value of a child component, without using props.
useRef and createRef
When working with class-based components, we use createRef() to create a ref. However, now that React recommends functional components and general practice is to follow the hooks way of doing things, we don't need to use createRef(). Instead, we use useRef() to create refs in functional components.
The first difference between useRef and createRef is that createRef is typically used when creating a ref in a class component while useRef is used in function components. Additionally, createRef returns a new ref object each time it is called while useRef returns the same ref object on every render.
Another main difference is that createRef doesn't accept an initial value, so the current property of the ref will be initially set to null. On the other hand, useRef can accept an initial value and the current property of the ref will be set to that value.
Adding a ref to your component
Class component
Functional component
You can add a ref to your component by importing the useRef Hook from React:
Then, use it to declare a ref inside your component:
useRef returns an object like this:
Refs and the DOM
You can point a ref to any value. However, the most common use case for a ref is to access a DOM element. For example, this is handy if you want to focus an input. When you pass a ref to a ref attribute in JSX, like <input ref={myRef}>
, React will put the corresponding DOM element into myRef.current.
The useRef hook returns an object with a single property called current. Initially, myRef.current will be 0. When React creates a DOM node for this <input>
, React will put a reference to this node into myRef.current. You can then access this DOM node from your event handlers and use the built-in browser APIs defined on it.
Accessing another component’s DOM nodes
If you try to put a ref on your own component, by default you will get null. Here is an example demonstrating it where clicking the button does not focus the input.
and it will throw an error in the console while the app is rendering:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
This happens because by default React does not let a component access the DOM nodes of other components.
Instead, components that want to expose their DOM nodes have to opt into that behavior. A component can specify that it “forwards” its ref to one of its children. Here’s how MyInput can use the forwardRef API:
Ref forwarding is a technique to automatically pass a ref to a child component, allowing the parent component to access that child component’s element and read or modify it in some way.
Differences between refs and state
Perhaps you’re thinking refs seem less “strict” than state - you can mutate them instead of always having to use a state setting function, for instance. But in most cases, you will want to use state. Refs are an “escape hatch” you won’t need often. Here’s how state and refs compare:
Refs | State |
---|---|
useRef(initialValue) returns { current: initialValue } | useState(initialValue) returns the current value of a state variable and a state setter function ( [value, setValue]) |
Doesn’t trigger re-render when you change it. | Triggers re-render when you change it. |
Mutable - you can modify and update current’s value outside of the rendering process. | “Immutable” - you must use the state setting function to modify state variables to queue a re-render. |
You shouldn’t read (or write) the current value during rendering. | You can read state at any time. However, each render has its own State as a Snapshot – React of state which does not change. |
Best practices for refs
Following these principles will make your components more predictable:
- Treat refs as an escape hatch. Refs are useful when you work with external systems or browser APIs. If a lot of your application logic and data flow relies on refs, you might want to rethink your approach.
- Don’t read or write
ref.current
during rendering. If some information is needed during rendering, use state instead. Since React doesn’t know when ref.current changes, even reading it while rendering makes your component’s behavior difficult to predict. (The only exception to this is code like if (!ref.current) ref.current = new Thing() which only sets the ref once during the first render.)
useImperativeHandle hook
useImperativeHandle is a React Hook that lets you customize the handle exposed as a ref.
useImperativeHandle(ref, createHandle, dependencies?)
Call useImperativeHandle at the top level of your component to customize the ref handle it exposes:
Parameters:
- ref: The ref you received as the second argument from the forwardRef render function,
- createHandle: A function that takes no arguments and returns the ref handle you want to expose. That ref handle can have any type. Usually, you will return an object with the methods you want to expose,
- optional dependencies: The list of all reactive values referenced inside of the createHandle code. Reactive values include props, state, and all the variables and functions declared directly inside your component body
Usage
Exposing a custom ref handle to the parent component
By default, components don’t expose their DOM nodes to parent components. For example, if you want the parent component to have access to the DOM node, you have to opt in with forwardRef . Like this, a ref to the parent component will receive the DOM node. However, you can expose a custom value instead. To customize the exposed handle, call useImperativeHandle at the top level of your component.
In case you don’t want to expose the entire DOM node you can use useImperativeHandle to expose a handle with only the methods that you want the parent component to call as below:
Exposing your own imperative methods
The methods you expose via an imperative handle don’t have to match the DOM methods exactly. For example, this Post component exposes a scrollAndFocusAddComment method via an imperative handle. This lets the parent Page scroll the list of comments and focus the input field when you click the button.