React handles DOM updates automatically to reflect the render output of your components, reducing the need for direct manipulation. However, in certain cases, you may require access to the DOM elements managed by React, such as focusing on a node, scrolling to it, or determining its size and position. Since React doesn't provide built-in methods for these tasks, you'll need to use a ref to access the DOM node directly.
To access a DOM node managed by React, you need to first create a ref instance with the help of useRef hook, then pass it to desired element.
Component.jsx
import { useRef } from 'react';
// ...
const Component = () => {
const myRef = useRef(null);
//...
return (
//...
<div ref={myRef}>
)
}
The useRef Hook creates an object with a single property which is current. myRef.current will be null initially. When React creates a DOM node for this <div>, React will pass a reference to this node into myRef.current. After that you can then access this DOM node from your event handlers and use the built-in browser APIs defined on it.
For example, let's lets consider scrollIntoView API:
myRef.current.scrollIntoView();
In the below illustration, clicking the button will focus the input:
Form.jsx
import { useRef } from 'react';
const Form = () => {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<main>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</main>
);
}
export default Form;
Let's clarify the steps:
Let's move on with a case where multiple refs exist. In the below illustration, there are three images and three buttons. Each button centers corresponding image by using the browser scrollIntoView() method on the corresponding DOM node:
Carousel.jsx
import { useRef } from 'react';
const Carousel = () => {
const firstImgRef = useRef(null);
const secondImgRef = useRef(null);
const thirdImgRef = useRef(null);
const scrollBehavior = {
behavior: 'smooth',
block: 'nearest',
inline: 'center'
}
const handleScrollToFirstImg = () => {
firstImgRef.current.scrollIntoView(scrollBehavior);
}
const handleScrollToSecondImg = () => {
secondImgRef.current.scrollIntoView(scrollBehavior);
}
const handleScrollToThirdImg = () => {
thirdImgRef.current.scrollIntoView(scrollBehavior);
}
return (
<section>
<nav>
<button onClick={handleScrollToFirstImg}>
Tom
</button>
<button onClick={handleScrollToSecondImg}>
Maru
</button>
<button onClick={handleScrollToThirdImg}>
Jellylorum
</button>
</nav>
<div>
<ul>
<li>
<img
src="first-img-url"
alt="first-img"
ref={firstImgRef}
/>
</li>
<li>
<img
src="second-img-url"
alt="second-img"
ref={secondImgRef}
/>
</li>
<li>
<img
src="third-img-url"
alt="third-img"
ref={thirdImgRef}
/>
</li>
</ul>
</div>
</section>
);
}
Ok, so far we only covered using refs inside of a component and passing them to built-in HTML elements like <div />, <input />, etc.
What about passing ref to some custom component, like <InputComponent />. If you try to pass a ref on your own custom component, by default you will get null. Below is an illustration of it:
InputComponent.jsx
const InputComponent = (props) => {
return <input {...props} />;
}
Form.jsx
import {useRef} from 'react';
import InputComponent from "./InputComponent.jsx"
const Form = () => {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
}
return (
<div>
<InputComponent ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</div>
);
}
If we run this code and click the button, you will notice input does not get focus state when button is clicked.
React also prints an error to the console to help you notice the issue:
This occurs because React inherently restricts a component from directly accessing the DOM nodes of other components, including its own children. This restriction is intentional. Refs serve as an escape mechanism but should be employed carefully. Engaging in manual manipulation of another component's DOM nodes increases the fragility of your code.
Instead, components that want to expose their DOM nodes have to adapt to that behavior. By using forwardRef, component can indicate that it forwards its ref to one of its children.
Below is an illustration of how InputComponent is adjusted to this situation with using forwardRef API:
InputComponent.jsx
const InputComponent = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
Let's clarify this process:
Now, clicking the button to focus the input operates as expected.
In React, each update cycle comprises two phases:
It's generally not recommended to access refs during rendering, including those containing DOM nodes. During the initial render, DOM nodes haven't been created yet, so ref.current will be null. Similarly, during subsequent updates, the DOM nodes haven't been updated, making it stale to read them.
React assigns values to ref.current during the commit phase. Before updating the DOM, React resets the affected ref.current values to null. After updating the DOM, React immediately assigns them the corresponding DOM nodes.
Usually, refs are accessed from event handlers as a best practice.
Thank you for reading this far. See you in the next chapter :)