How to update Child's state from Parent in React.
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
Counter
component has acount
state which is displayed on the same. - The
setCount
function is forwarded onto theApp
component using theuseImperativeHandle
hook. useImperativeHandle
takes in theref
object and a callback function that returns an object that will be stored in thecurrent
property of theref
object.setCount
is now accessible onApp
and it can be used to update thecount
state.
There you go, The count
state in the Counter
component will now be updated from the App
component.
Live demo
Subscribe to my newsletter
Read articles from Arjun VC directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Arjun VC
Arjun VC
Typescript, React, and more...