I am trying to understand "forwardRef" more deeply referring the below page;
https://react.dev/learn/manipulating-the-dom-with-refs
Here it is mentioned that
A component doesn’t expose its DOM nodes by default. You can opt into exposing a DOM node by using forwardRef and passing the second ref argument down to a specific node.
My question is say for the example below;
import { forwardRef, useRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
We have
const MyInput = forwardRef()
But shouldn’t function Form() be specified also with forwardRef(), since inside Form component definition also, we have below i.e. it is also passing down it’s ref attribute.
<MyInput ref={inputRef} />
Basically, I am trying to understand where/how exactly is the "forwardRef" specified.
P.S: I understand things are different with the latest React v19, but just trying to understand since I have apps on older React versions;
2
Answers
You are defining
inputRef
at the parent level which will reference<input>
element at the child level via theforwardRef
.In your case,
Form()
is a parent component andMyInput
is the child component.So
Form()
wouldn’t need aref
passed to it. It defines it viauseRef
instead.MyInput
would needref
passed down from the parent component definition andforwardRef
wrapping it in its own definition.The confusion may be coming from not realizing
forwardRef
is a a function that will return a React element which is "the child element" you want to reference at the parent level.Answer to the question is :
a. As the parent component of Form component has not requested DOM element access to it, it should not wrap in forwardRef.
b. We need to wrap MyInput in forwardRef:
b.1) Since its parent Form component has requested DOM element
access to it.
b.2) The component MyInput cannot provide DOM access by itself since it is a function component.
c. We do not need to wrap input element in forwardRef, since it is not a function component therefore it can directly provide access to its DOM element.
A detailed answer:
The above point quoted from your post, may be better discussed under the background of component having nested components in 2 levels.
Please see below an example of this kind:
App.js
Test run – Output
Observation:
The button at the form level will clear inputs in all sections. Similarly, the button in each section will clear the input with respect to each section. Therefore component has been found functioning well as intended.
Let us now view the Render Tree of the sample App. It will assist you to get a clear view of the data flow discussed in the coming sections.
The Render Tree
Let us try to start discussing the code.
React documentation states this:
Let us inspect how will this statement hold true in each component in the app.
1. Root component
This component neither receives nor defines a ref object. Therefore it does come under the discussion.
2. Form component
This component does not receive a ref object. However it defines two ref objects. And it also declares two children, and it then requests to get access to each child component by the following statement.
It means the code intents to get reference to each MySection component. However, as we know, MySection is a function component. A function component may or may not have a single representation in DOM. A function component may be composed of other function components as well as HTML elements. For security reasons also React does not allow cross component DOM elements access. Therefore the intent of the code can be fulfilled only with the additional instructions in the source program.
Therefore we are in need of the function forwardRef, which is invoked as below.
By the code, we could make the call to forwardRef and could get the ref in Form component made available in MySection by the props ref. Now let us see what happens in MySection component.
3. MySection component
Here in this component, the very same ref received has been used to get access to the function component MyInput. Please note that there is no new ref objects defined in this component. It references the very same ref object it received as its ref prop.
However the same issue happens here as well. MyInput is a function object which is comprised another function component MyInput. Therefore we need to provide further instructions which is shown below.
By the code, we could make the call to forwardRef and could make the same ref in MySection, available in MyInput as well, by the props ref. Now let us see what happens in MyInput component.
4. MyInput component
MyInput receives the same ref passed through MySection. However this function component contains the HTML element which has been requested by Form component. Therefore it can fulfil the request without any additional forward calls. Therefore the following line of code actually fulfils the request for access.
Final notes:
Please take note that there are only two ref objects created in the entire app, which have been defined in Form component. MySection as well as MyInput components are just receiving the same ref, since it forward the ref. And finally MyInput fulfils the request for access.
Though we say it fulfils the request for access, during the entire rendering of the App, the value in these two ref in all components will be undefined. The reason for that is that ref objects are set by React only during the commit stage which follows rendering stage. It means only during commit, the DOM elements are created or updated.