How to update Child's state from Parent in React.

Arjun VCArjun VC
3 min read

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 a count state which is displayed on the same.
  • The setCount function is forwarded onto the App component using the useImperativeHandle hook.
  • useImperativeHandle takes in the ref object and a callback function that returns an object that will be stored in the current property of the ref object.
  • setCount is now accessible on App and it can be used to update the count state.

There you go, The count state in the Counter component will now be updated from the App component. Live demo

12
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...