When using React, our default mindset should be that we don't imperatively modify the DOM, but instead that we pass in props and then re-render the component. But sometimes there are situations where imperative actions are necessary.
Refs in React provides a way to access the React elements (or DOM nodes) created in the render()
method.
When parent components need to interact with children components, we typically use props However, in some cases we might need to modify a child without re-rendering it with new props. That's when refs get the spotlight.
When Should I use Refs?
We advise to use refs in the following situations:
- Integrating with third-party DOM libraries.
- Triggering imperative animations.
- Managing focus, text selection, or media playback.
So once we've determined that we really should be using refs, how do we use them?
Using Refs in React
There are various ways in which you can use refs:
React.createRef()
- Callback refs
- String refs (legacy)
- Forwarding refs
Let's look at each one of these in turn.
- React.createRef()
- Focus an Input using Refs
- Getting Values from a
ref
- Callback Refs
- String Ref (Legacy API)
- Conclusion
React.createRef()
Refs can be created by using the React.createRef()
function and attached to an HTML element in a React component via the ref
attribute.
A ref is usually created inside a component's constructor so as to make it usable throughout the component. For example:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.firstRef = React.createRef();
}
render() {
return <div ref={this.firstRef} />;
}
}
As you can see above:
- a ref instance is created in the constructor as
this.firstRef
and - it's assigned to a
ref
in thediv
inside therender()
function. Let's look at an example of how to use refs in a React component.
Focus an Input using Refs
Here's another example:
// Ref.js
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
In the code block above, we've built a button that automatically focuses on an input field when it's clicked on.
We start by creating a ref
instance and setting it to this.textInput
in the constructor method and then assigning it to the input
field via the ref
attribute.
<input type="text" ref={this.textInput} />
Note that when the ref
attribute is used on an HTML element (in this case the input
field), the ref
created in the constructor
(with React.createRef()
) receives the underlying DOM element in the current
value.
This means to access the DOM value, we need to write something like this:
this.textInput.current;
The second input field is the button that will be clicked on to auto focus on the first input field. It has an onClick
attribute set to the this.focusTextInput
function.
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
The focusTextInput()
function uses the JavaScript standard DOM function .focus()
to focus the cursor on the text input box.
focusTextInput() {
this.textInput.current.focus();
}
Lastly, the focusTextInput
function is bound in the constructor
method like this:
this.focusTextInput = this.focusTextInput.bind(this);
Getting Values from a ref
In this example, we'll see how to set an input
field to a ref
and then get the value of the ref
. Here's what the example will look like:
In this example, we create an input
field where we enter a value. Then, when the submit button is clicked, we'll read this value and log it to the console.
// Ref.js
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
}
handleSubmit = e => {
e.preventDefault();
console.log(this.textInput.current.value);
};
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<input type="text" ref={this.textInput} />
<button>Submit</button>
</form>
</div>
);
}
}
Again, we use the React.createRef()
function to create a ref instance and then we assign it to the instance variable this.textInput
.
In the render
function, the form
contains the input
field whose value
we want to read. How do we read this value? By assigning a ref
to the input
and then reading the value from that ref
.
<input type="text" ref={this.textInput} />
The form
has an onSubmit
function of this.handleSubmit
which logs the value of the input field to the console.
handleSubmit = e => {
e.preventDefault();
console.log(this.textInput);
};
Above, the
e
parameter contains the event object. We usee.preventDefault()
to tell our browser that we are dealing with the submit button being clicked and we don't want this event to "bubble up" (that is, be handled by the browser outside of this code).
In the interactive example if we log this.textInput
we're given an Object -- this is the ref object:
> Object {current: HTMLInputElement}
Notice that it has one property current
, which is an HTMLInputElement
. This is the input
DOM element itself and not the actual value. To get at the value of the input
tag, we have to acess this.textInput.current.value
as seen below:
handleSubmit = e => {
e.preventDefault();
console.log(this.textInput.current.value);
};
Using ref
s is a straightforward way to get the values from form controls: just assign a ref
to an input field and extract the value when you need it.
Callback Refs
The callback ref is another way of using refs in React. To use refs in this way we set the ref property to a callback function. When we set a ref, React will call this function, passing the element
as the first argument.
Here's the code for another example. Like previous example above this code gets the text value of an input
tag, but here we use callback refs instead:
// Refs.js
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
}
handleSubmit = e => {
e.preventDefault();
console.log(this.textInput.value);
};
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<input type="text" ref={this.setTextInputRef} />
<button>Submit</button>
</form>
</div>
);
}
}
In the example above, the input
tag has a ref
set to this.setTextInputRef
. this.setTextInputRef
.
React will call the ref
callback with the DOM element when the component mounts, and call it with null
when it unmounts. (ref
callbacks are invoked before componentDidMount
and componentDidUpdate
lifecycle hooks.)
String Ref (Legacy API)
There is another way to set refs, but it's considered legacy and likely to be deprecated soon. But you might see it in other people's code, so it's worth mentioning here.
With string refs, you'll see the markup of the input tag set to something like:
<input type="text" ref="textInput" />
And then on the component, we'd get the value like this: this.refs.textInput.value
- but, again, this should not be done in new code as the API will be deprecated.
Forwarding Refs
Ref forwarding is a technique for passing a ref through a component to one of its children. It's very useful for cases like reusable component libraries and Higher Order Components (HOC).
You can forward a ref to a component by using the React.forwardRef
function. Let's look at an example below:
// Ref.js
const TextInput = React.forwardRef((props, ref) => (
<input type="text" placeholder="Hello World" ref={ref} />
));
const inputRef = React.createRef();
class CustomTextInput extends React.Component {
handleSubmit = e => {
e.preventDefault();
console.log(inputRef.current.value);
};
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<TextInput ref={inputRef} />
<button>Submit</button>
</form>
</div>
);
}
}
Ref forwarding allows components to take a ref
they receive and pass it further down (in other words, "forward" it) to a child.
In the example above, we have a component called TextInput
that has a child which is an input
field. So how do we pass or forward the ref
down to the input
?
First, we start by creating a ref
with the line of code below:
const inputRef = React.createRef();
Then, We pass our ref
down to <TextInput ref={inputRef}>
by specifying it as a JSX attribute. React then forwards the ref
to the forwardRef
function as a second argument.
Next, We forward this ref argument down to <input ref={ref}>
. The value of the DOM node can now be accessed at inputRef.current
.
Forwarding refs and higher-order components
Finally, let's look at another example of using refs but this time with Higher Order Components (HOC).
In the example app above, every keypress in the input field is logged to the console. The input field has a ref
assigned to it and the idea is to see how refs are passed/forwarded down in Higher Order Components.
const Input = InputComponent => {
const forwardRef = (props, ref) => {
const onType = () => console.log(ref.current.value);
return <InputComponent forwardedRef={ref} onChange={onType} {...props} />;
};
return React.forwardRef(forwardRef);
};
Here there is a Higher Order Component named Input
which accepts InputComponent
as an argument. It also logs the value of the ref
to the console on every keypress.
In the Input
HOC, the forwardRef
function returns the InputComponent
. A ref
is then created by the React.forwardRef
function which contains the forwardRef
function. The Input
component will return the value.
Next we create the component that will be the child of the Input Higher Order Component.
const TextInput = ({ forwardedRef, children, ...rest }) => (
<div>
<input ref={forwardedRef} {...rest} />
{children}
</div>
);
The component above has forwardedRef
assigned to ref
so that the input
field can accept a ref
when rendered in a child component. The destructured ...rest
allows us to spread the props (that is, pass all arguments in the rest
array down as props to the input
tag). So how do we use the TextInput
component? Like this:
const InputField = Input(TextInput);
class CustomTextInput extends Component {
render() {
const inputRef = React.createRef();
return <InputField ref={inputRef} />;
}
}
Finally, the Input
Higher Order Component along with it's child component, TextInput
is set to the InputField component
.
The InputField
component is then rendered with ref
being passed to it.
Conclusion
Refs are a great way to pass data down to a particular child instance in a way that's different from via props and state.
You have to be careful because refs
manipulate the actual DOM as opposed to virtual DOM which is contradictory to the React mindset. So while refs
shouldn't be the default method for flowing data through your application, they can be a great way to read data from DOM elements, when you have to.