1. What's the difference between Comp1 and Comp2 refs?
Comp1
uses React.forwardRef
and will be able to receive a given ref
from parent.
Comp2
won't work and trigger following error:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
?
<Child ref={ref1}/>
is like a request from parent component: "Hey Child
, please pass your component reference or similar into given mutable store box (ref
), so I can invoke a method on you or manipulate a contained DOM node directly."
Problem - there is no
instance for function components:
// React internally calls `new ClassComp()`, instance can be stored and passed in a ref
<ClassComp ref={compRef} />
// React just *calls* the function for re-renders, there is no plain function "instance"
<FunctionComp ref={compRef} />
React.forwardRef
solves above limitation. With
const FunctionComp = React.forwardRef((props, ref) => <div ref={ref}>Hello FnComp</div>
, FunctionComp
can still pass something representative to the ref
given from Parent
(like div
DOM node), despite having no instance. ClassComp
instead passes its instance here.
2. Why do I have to use forwardRef
along with useImperativeHandle
in order to actually get the ref to Comp1
?
You don't have to. useImperativeHandle
is an extension to provide a more custom imperative call API. Following three alternatives are equivalent:
forwardRef
only:
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp ref={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
return <input ref={ref} />;
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle
only, with custom ref prop (take a look at
this answer for more info):
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp customRef={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = ({ customRef }) => {
const inputRef = React.useRef();
React.useImperativeHandle(customRef, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle
+
forwardRef
:
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp ref={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>