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.