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.firstRefand -
it's assigned to a
refin thedivinside 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
eparameter 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 refs 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.