forwardRef
is a helper function from React that allows us to forward a component's ref to another one.
This tutorial will teach what all of that means and how to use the function, including the correct TypeScript definitions.
What are refs in React again?
Before we start, let's do a quick recap of what refs (or references) in React are.
We will usually use references in React to store the reference to a DOM element, such as a button or an input field.
Having the element available to us as a variable in our code allows us to use browser APIs only available through the DOM.
Let's say we want to programmatically focus an input element when the user clicks a button. React doesn't have a built-in way of doing this, but in plain JavaScript, we would query the DOM to find the element first and then call the focus
function that's part of the element.
const inputElement = document.getElementById('input-element-id');
inputElement.focus();
React makes these operations a bit easier by allowing us to store references to DOM elements like this:
const App = () => {
const inputRef = useRef(null);
return (
<>
<input
ref={inputRef} value={name}
onChange={handleChange}
/>
<button
onClick={() => inputRef.current.focus()}
>
Focus
</button>
</>
);
};
We don't need to assign an ID to our input element anymore, as we can pass down a ref
variable to the DOM element. React will then assign the element to this variable.
The ref
object will look like the following after React assigned the element.
{ current: input }
Make sure that you pass down a variable that has been initialized with useRef
(or createRef
in class components).
If we now want to focus input element, we just need to call inputRef.current.focus()
.
You can check out this codepen to try it out yourself.
If you want to learn more about references and the useRef
hook, I recommend checking out my other article on the topic.
Why do we need forwardRef in React?
So what is forwardRef, and why is it important?
Let's start with an example: Let's say we have an Input
component in our React application that looks like the following:
const Input = (props) => {
return <input {...props} />;
};
Now, we also want to focus the input component on the click of a button.
To achieve that, we simply need to create a new reference in our app, pass it down to Input
, and call .focus()
on the element, right? Wrong!
By default, the ref
prop only works on HTML elements, not on React components.
When we want to pass down a reference to a React component, we need to tell React which HTML element it should reference, as there can be more than one in our component.
That's where forwardRef
becomes useful. It allows us to specify which exact HTML element we want to reference.
How to use forwardRef
Our task is to forward the ref that the Input
component receives to the HTML input element.
We do that by wrapping the component in forwardRef
. The function accepts a callback with two parameters:
- The component props
- The ref to be forwarded
This callback is our function component, which now accepts a second property.
const Input = forwardRef((props, ref) => {
// Here goes the content of our component
});
In the returned JSX code, we now need to pass the ref we receive in the function to the correct HTML component, which in our case is the input
element.
const Input = forwardRef((props, ref) => {
return (
<input
ref={ref} {...props}
/>
);
});
Et voilá, focussing the input via refs works!
Here's the code for doing so:
import React, { useState, useRef, forwardRef } from 'React';
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
const App = () => {
const inputRef = useRef(null);
const [value, setValue] = useState('');
const onInputChange = (e) => {
e.preventDefault();
setValue(e.target.value);
};
const focus = () => {
inputRef.current?.focus();
};
return (
<>
<Input value={value} onChange={onInputChange} ref={inputRef} />
<button onClick={focus}>Focus</button>
</>
);
};
Custom names for DevTools
The forwardRef
function takes the name of the component from the function it accepts as a parameter. If you're using anonymous or arrow functions, your DevTools will display the component like this:
There are a few ways to avoid this. First, you could use a regular function expression with a name like this:
const Input = forwardRef(function Input(props, ref) {
return <input ref={ref} {...props} />;
});
If you're like me and haven't used this syntax in the last three years, you can also set the display name manually by adding the following line to the end of the file:
Input.displayName = 'Input';
Now we have covered the primary use-cases of forwardRef
. Continue reading if you want to know how to use it with TypeScript, class components, or higher-order components.
How to use forwardRef with class components
Unfortunately, we can't just wrap our class components in forwardRef
.
However, we can use our class components in the render function that we provide to forwardRef
.
class Input extends React.Component {
const { innerRef, ...props } = this.props;
render() (
return (
<div style={{backgroundColor: "blue"}}>
<input ref={innerRef} {...props} />
</div>
)
)
}
const InputForwardingRef = forwardRef((props, ref) => (
<Input {...props} innerRef={ref}/>));
There's one crucial difference here: we can't use the ref
prop as this will only work if the component uses forwardRef
. We need to come up with an alternative name, like innerRef
in this example.
Forwarding refs in higher-order components
As we have seen with standard React components, the ref prop is unique as we can only access it through the forwardRef
function.
The same applies to higher-order components (HOCs) as well.
Let's say you have a HOC that passes all props through to the component it is wrapped around.
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return LogProps;
}
This function won't pass the ref
prop to WrappedComponent
, though. To handle this case as well, we need to make use of forwardRef
again.
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />; }
}
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; });}
If you want a more detailed explanation of how to use refs with higher-order components, I recommend you check out the React documentation, which does a great job explaining this topic.
How to use forwardRef with TypeScript
With TypeScript, we can define what type of reference we expect, which is very helpful, as an input element provides different functions than a button, for instance.
forwardRef
accepts two generic types. The first one defines the type of the reference, while we can use the second one for the component props.
const Input = forwardRef<HTMLInputElement, IInputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
Alternatively, we can also add the type definitions to the callback function.
The second argument needs to be of the type React.Ref
, which is the generic type definition for references. We can then pass the type of the HTML element to the generic type (Ref<HTMLInputElement>
).
const Input = forwardRef((props: IInputProps, ref: Ref<HTMLInputElement>) => {
return <input ref={ref} {...props} />;
});
When using such a strongly-typed component, we need to ensure that the type of the ref
that we pass down to the component matches the expected HTML element.
Without a proper type, we'll also run into issues when trying to access functions or properties of our ref, which will result in type errors.
With useRef
, we can define the type of the reference like this:
const inputRef = useRef<HTMLInputElement>(null);
Conclusion
As the ref
prop doesn't behave like other props, it can be confusing when dealing with React's references, but once we understand how to use forwardRef
, it becomes a lot easier.
I hope that this tutorial could answer all the questions you had about forwardRef
and references. If not, you may check out my other article on using useRef or leave a comment if I'm missing something. Thanks for reading!
Further reading:
Using setTimeout in React components (including hooks)
Using timeouts in React isn't as straightforward as you may think. In this article, you'll learn how to avoid mistakes when using them in React.
When does React re-render components?
In this article, I explain in detail everything you need to know about rendering in React.