How to update Child's state from Parent in React.
Pass a value from child to parent with the help of forwardRef & useImperativeHandle

So as you have made it here, let me assume that you know React to an extent that I don't have to explain what a state is or how props are passed. If you aren't confident enough, head on to reactjs.org and get started with the basics of React.
Let's get straight to the point, No boring introduction this time.
Before you can perform this stunt, You need to have a basic idea on a couple of concepts.
1. forwarRef
In React, every native JSX element accepts a ref prop which returns that particular element after it is rendered. By native, I mean standard HTML element. This element(object) is usually stored in a ref object that is initialized using the useRef hook. It will look something like this
const App = () => {
const buttonRef = useRef<HTMLButtonElement>(null);
return <button ref={buttonRef}>Click Me</button>
}
This is for native HTML elements. For custom React components, you will have to define the ref prop. forwardRef is what React provides for this purpose. Using forwardRef you can pass a ref through a component to one of its children.
const CustomButton = forwardRef<HTMLButtonElement>((_props, ref) => {
return <button ref={ref}>Click me</button>;
});
const App = () => {
const buttonRef = useRef<HTMLButtonElement>(null);
return <CustomButton ref={buttonRef} />
}
Notice how we have passed the button element inside CustomButton onto the parent App component. Now, what if we wanted to pass some other value instead of the element itself? React provides a hook for this purpose.
2. useImparativeHandle
React makes it possible to pass a value from child to parent through ref using the useImparativeHandle hook. Note that even React suggests not to use this pattern as the data flow is supposed to be unidirectional (Top to Bottom/Parent to Child).
const Parent = () => {
const childRef = useRef<{ value: string }>(null);
useEffect(() => console.log(childRef.current?.value), [childRef]);
// I am from child
return <Child ref={childRef} />;
};
const Child = forwardRef<{ value: string }>((_props, ref) => {
useImperativeHandle(ref, () => ({ value: "I am from child" }));
return <></>;
});
And this is all we need! Let's create that typical counter app used in every React blog ever, but this time the buttons will be on the parent component.
// Counter.tsx
import { forwardRef, useState, useImperativeHandle } from "react";
export interface ICounter {
setCount: React.Dispatch<SetStateAction<number>>;
}
export const Counter = forwardRef<ICounter | null>((_props, ref) => {
const [count, setCount] = useState(0);
useImperativeHandle(ref, () => ({
setCount
}));
return <span>{count}</span>;
});
// App.tsx
import { useRef } from "react";
import { Counter, ICounter } from "./Counter.tsx";
export const App = () => {
const counterRef = useRef<ICounter | null>(null);
const handleCounterChange = (step: number) =>
counterRef.current?.setCount((count) => count + step);
return (
<div>
<button onClick={() => handleCounterChange(-1)}>-</button>
<Counter ref={counterRef} />
<button onClick={() => handleCounterChange(1)}>+</button>
</div>
);
};
Code Explaination
- The
Countercomponent has acountstate which is displayed on the same. - The
setCountfunction is forwarded onto theAppcomponent using theuseImperativeHandlehook. useImperativeHandletakes in therefobject and a callback function that returns an object that will be stored in thecurrentproperty of therefobject.setCountis now accessible onAppand it can be used to update thecountstate.
There you go, The count state in the Counter component will now be updated from the App component.
Live demo
