X
Popular Searches

How to Use Refs in React

React logo on a dark background

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.

Advertisement

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} />
    }
}
Advertisement

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} />
));
Advertisement

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.

Advertisement

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.

James Walker James Walker
James Walker is a CloudSavvy IT contributor. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows with DevOps, CI/CD, Docker, and Kubernetes. Read Full Bio »

The above article may contain affiliate links, which help support CloudSavvy IT.