Quick Links
Refs are a React feature which let you directly access DOM nodes created by a component's
render()
method. They provide a way to break out of React's declarative rendering so you can call browser APIs.
When working with React, you usually declare what a component's DOM should look like using JSX:
class MyComponent extends React.Component {state = {value: ""};
handleChange = e => this.setState({value: e.target.value});
render() {
return (
<div>
<h1>Hello World!</h1>
<input
onChange={this.handleChange}
value={this.state.value} />
</div>
);
}
};
Internally, React transpiles the JSX to work out how to manipulate the DOM. It'll use browser functions such as
document.createElement()
and
document.appendChild()
to create the DOM structure you've declared.
This drastically simplifies the code you need to programmatically update the DOM. To modify elements, you change their props or update a component's state. React then calculates the differences and makes the DOM adjustments necessary.
The Case For Refs
You don't normally get to access the DOM nodes created by React. In the example above, we have no handle to the elements we've created. This becomes problematic if you want to call a DOM API which can't be accessed declaratively via React props.
Let's consider what happens if the user enters an invalid value into the text input. If they then clicked submit in a real form, it would be a good idea to show an error message and put the focus back into the text input. You can't do this without access to the input's DOM node. You need to call the
focus()
method on that element.
Enter refs. Refs give you a first-class way to gain a "reference" to React's DOM nodes. You can solve the focus issue by assigning a ref to the input. Refs are objects with a
current
property that contains the DOM node they refer to.
Creating a Ref
Refs are usually created explicitly by calling
React.createRef()
. You then assign them to a component instance using the special
ref
prop. This isn't a real prop and can't be accessed by the component.
class DemoComponent extends React.Component {inputRef = React.createRef();
focusInput = () => this.inputRef?.current.focus();
render() {
return (
<div>
<input ref={this.inputRef} />
<button onClick={this.focusInput} />
</div>
);
}
}
The ref gets assigned to the instance property
inputRef
. This is then handed to the
input
element by setting its
ref
prop. When the button is clicked, the
focusInput()
method gets called. This accesses the
current
property of the ref, which contains the input's actual DOM node. It can now call
focus()
to focus the text field.
The
current
property of refs can be
null
. This occurs when the ref isn't assigned to a rendered DOM element. In this example,
inputRef.current
will be
null
until the
render()
method is called and the input picks up the ref. For this reason, the optional chaining operator (
?.
) is used in
focusInput()
to gracefully handle the scenario where the ref doesn't yet refer to anything.
Assigning Refs to React Components
The example above shows how refs work when used with plain HTML elements. You can also assign a ref to React component instances. This allows you to directly call methods defined by children you render.
class View extends React.Component {state = {
error: true; // Example!
};
formRef = React.createRef();
focusForm = () => this.formRef?.current.focusInput();
submit = () => {
if (this.state.error) {
alert("There was an error; check your input.");
this.focusForm();
}
};
render() {
return (
<div>
<Form ref={this.formRef} />
<Button onClick={this.submit} />
</div>
);
}
}
class Form extends React.Component {
inputRef = React.createRef();
focusInput() {
this.inputRef.current?.focus();
}
render() {
return <input ref={this.inputRef} />
}
}
In this scenario, the
current
property of the
formRef
in
View
doesn't refer to a DOM node. Instead, it references the
Form
component instance which has been rendered.
You should take care when using this approach. Always pass data to child components via props, instead of using refs as a callback system.
Generally, a ref should be used when direct DOM interaction is unavoidable. This remains true when assigning a ref to a React component instance. You shouldn't call arbitrary component methods via a ref.
Our example meets this requirement -
Form
is a presentational component, whereas
View
is a complex container that could be rendering multiple forms. It needs to be able to set the focus to problematic fields, even though it's not directly rendering them. The solution is to use a component ref carefully, ensuring the usage is justified by a need to manipulate the DOM.
Refs and Functional Components
Functional components cannot receive refs. They don't have instances so there's nothing to assign the ref to. However, you can use forwarding to redirect a ref to a DOM component.
Ref forwarding is an optional feature that lets a component pass a ref it receives to one of the children it renders. To forward a ref, wrap your component with a call to React's
forwardRef()
function:
const InputComponent = React.forwardRef((props, ref) => (<input ref={ref} value={props.value} />
));
forwardRef()
accepts a function that should return a React component. The function will be called when it needs to render and will be passed two parameters: its props and its forwarded ref.
Using Refs Within Functional Components
Although functional components can't directly receive refs, they can create them with the
useRef()
hook. This is equivalent to the
createRef()
method available in class components.
const InputComponent = props => {const ref = useRef();
return <input ref={ref} value={props.value} />
};
Importantly,
useRef()
can be used for more than just refs. Its real role is to provide a way of persisting data between calls to a functional component. It returns an object with a
current
property which React then maintains and restores each time the component renders.
You can therefore use
useRef()
to persist arbitrary data in functional components. Set the
current
property of the returned object to the value you want to keep around.
Callback Refs
A final way to work with refs is via the "callback" pattern. With this approach, you don't need to manually call
createRef()
or
useRef()
. Instead, you set the
ref
prop to a function that React will call during rendering. It'll pass the element's DOM node as the only argument to the function.
class CallbackRef extends React.Component {render() {
return <input ref={el => this.inputRef = el} />
}
}
This is a more concise way of creating refs which are assigned to instance properties. Your function receives the DOM node directly - there's no
.current
to handle, unlike
createRef()
.
Conclusion
React Refs let you handle scenarios you couldn't resolve with declarative rendering alone. They're your route to direct DOM manipulation when working with forms, media playback and animations. You'll also find yourself reaching for refs if you need to integrate a third-party JavaScript library that wasn't built for React.
Although refs are flexible, you shouldn't overuse them. Before creating a ref, check your components to make sure you can't achieve your goal declaratively.
Refs are a necessary part of React but they contradict the library's principles. Too many refs can quickly make your app complicated to maintain. They let you break the top-down data flow that props usually enforce.