Using refs in React components
React keeps a snapshot of the UI in memory (virtual DOM) and syncs it with the real DOM when necessary. It re-renders components when props are modified or state is updated. However, in certain cases, components can be modified without triggering the re-render. This is where React refs come in handy.
Refs
Refs provide direct access to DOM nodes or React elements which allows you to modify a component without re-rendering it. This technique is useful when, for instance, you need to manage input focus state or handle text selection. Another use case is integrating with third-party DOM libraries (more on this here).
class App extends Component {
constructor(props) {
super(props);
// [1] create a ref
this.username = React.createRef();
}
handleFormSubmission(e) {
e.preventDefault();
// [3] get the actual the input element node by accessing .current property
const node = this.username.current;
if (node && !node.value) {
// [4] set focus on the node if it doesn't have a value
// we can do whatever we want here e.g. apply styling,
// add event handlers, etc.
node.focus();
}
}
render() {
// [2] attach the ref to the input element
return (
<form onSubmit={(e) => this.handleFormSubmission(e)}>
<input ref={this.username}/>
<button type="submit">Login</button>
</form>
)
}
}
Something to note here is that refs cannot be used on functional components. A functional component is stateless and does not have any lifecycle methods. It does not have an instance, therefore, a ref cannot be attached to it. If you want to attach a ref to a functional component, convert it into a class first.
// [1] create a functional component
const UsernameInput = () => {
return (
<input name="username"/>
)
}
class App extends Component {
constructor(props) {
super(props);
// [2] create a ref
this.username = React.createRef();
}
render() {
// [3] attaching the ref to the functional component won't work as functional components do not have instances
return (
<form>
<UsernameInput ref={this.username} />
<button type="submit">Login</button>
</form>
)
}
}
Using refs inside of functional components is possible as long as a DOM element or class component is referenced.
// [1] create a functional component with a ref inside
const UsernameInput = () => {
const input = React.createRef();
// [3] set the value of the input element
const handleClick = function() {
input.current.value = 'Clicked';
}
// [2] attach the ref to the input element
return (
<input name="username" ref={input} onClick={handleClick} />
);
}
Ref forwarding
In some situations, we might want to create a ref outside of the component and then pass the ref into it. By doing so we can forward a ref to child components hence the name of this technique – ref forwarding. This is quite useful when we have reusable components and we want to manage things (like focus state, text selection outside, etc.) of the components themselves.
// [1] create a functional component and obtain the ref passed into it
const InputComponent = React.forwardRef((props, ref) => {
return <input ref={ref} />;
});
class Form extends Component {
constructor(props) {
super(props);
// [2] create a ref outside of the reusable child component
this.inputRef = React.createRef();
this.handleFormSubmission = this.handleFormSubmission.bind(this);
}
handleFormSubmission(e) {
e.preventDefault();
const username = e.target.username.value;
if (typeof username !== 'string' || !username.length) {
console.log('Username invalid');
}
}
componentDidMount() {
// [4] set focus on the input element inside the InputComponent when the Form component is mounted
this.formBtnRef.current.focus();
}
render() {
// [3] pass ref into the child component
return (
<form onSubmit={this.handleFormSubmission}>
<InputComponent ref={this.inputRef} />
<button type="submit">Add</button>
</form>
);
}
}
We can apply the same technique to HOCs (higher-order components).
// [1] create a functional component and obtain the ref passed into it
const InputComponent = React.forwardRef((props, ref) => {
return <input ref={ref} />;
});
const passRef = (WrappedComponent) => {
class NewComponent extends Component {
constructor(props) {
super(props);
this.state = { data: [] };
}
render() {
// [6] pass ref to the input component
return (
<form onSubmit={this.handleFormSubmission}>
<WrappedComponent ref={this.props.forwardedRef} />
<button type="submit">Add</button>
</form>
);
}
}
// [5] forward ref as a new forwardedRef prop to the NewComponent
return React.forwardRef((props, ref) => {
return <NewComponent forwardedRef={ref} {...props} />;
});
};
class HocComponent extends Component {
constructor(props) {
super(props);
// [2] create a ref
this.inputRef = React.createRef();
}
// [7] set focus on the input element (which is inside of the HOC component) on component mount
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return (
// [3] create a HOC
const hocComponent = passRef(InputComponent);
// [4] pass ref into the HOC component
return <hocComponent ref={this.inputRef} />;
);
}
}
Refs and ref forwarding is a great way to access DOM nodes and interact with child components without re-rendering them. Apply these techniques wisely and do not overuse them. Have fun!