Back
Featured image of post Tips for invoke child component function and force rerendering

Tips for invoke child component function and force rerendering

Have you ever face an issue in React where you wanted to invoke an inner function from the child component for rerendering? In this artcle, we will be going through some real world example on how the issue happened and solution for it.

Table of contents

Introduction

Say we have 2 components in our React application. One is the Parent component while the other is a Child component, and the parent component is supposed to pass in a prop for initial value to the Child component.

The Child component will then takes that prop value as a initial value for an useState hook.

For example, we have a counter value and setCounter function from a useState hook that takes a prop value from Parent component as a initial counter value.

// Parent component
import { useState } from "react";
import "./App.css";
import Child from "./Child";

function App() {
  const [initialCount, setInitialCount] = useState(5);
  return (
    <>
      <div>
        <Child initialCount={initialCount} />
        <button onClick={() => setInitialCount(0)}>Parent reset</button>
      </div>
    </>
  );
}

export default App;
// Child component
import { useState } from "react";

const childCounterDisplay = {
  display: "flex",
  alignItems: "center",
};

export default function Child({ initialCount }) {
  console.log("initialCount", initialCount);
  const [count, setCount] = useState(initialCount);

  const reset = () => {
    setCount(0);
  };

  return (
    <div>
      <div>
        Child counter: <span>{count}</span>
      </div>
      <div style={childCounterDisplay}>
        <button onClick={() => setCount((prevCount) => prevCount + 1)}>
          +
        </button>
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  );
}

Explaination:

We first pass in the initial value as 5, the increment function works fine in the Child component but if we click on the Parent reset button, the counter value will not set to 0 even the initialValue have changed from 5 to 0 in the console

The most common way is to move the useState hook from Child component out to the parent component. For example:

// Parent component
import { useState } from "react";
import "./App.css";
import Child from "./Child";

function App() {
  const [count, setCount] = useState(5);
  return (
    <>
      <div>
        <Child count={count} setCount={setCount} />
        <button onClick={() => setCount(0)}>Parent reset</button>
      </div>
    </>
  );
}

export default App;
// Child component
const childCounterDisplay = {
  display: "flex",
  alignItems: "center",
};

export default function Child({ count, setCount }) {
  return (
    <div>
      <div>
        Child counter: <span>{count}</span>
      </div>
      <div style={childCounterDisplay}>
        <button onClick={() => setCount((prevCount) => prevCount + 1)}>
          +
        </button>
        <button onClick={() => setCount(0)}>Reset</button>
      </div>
    </div>
  );
}

What if today’s the Child component is imported from a library where you can’t really move the useState hook out of this component?

React Keys

React Keys are used in React to identify which items in the list are changed, updated, or deleted. In most cases, it is being used as an identifier when we are creating lists of elements. However, we could also utilize this field to actually “tell” React to rerender the selected component.

// Parent component
import { useState } from "react";
import "./App.css";
import Child from "./Child";

function App() {
  const [childKey, setChildKey] = useState(0);
  return (
    <>
      <div>
        <Child initialCount={0} key={childKey} />
        <button onClick={() => setChildKey((prevKey) => prevKey + 1)}>
          Parent reset
        </button>
      </div>
    </>
  );
}

export default App;

useimperativehandle hook

Alternatively, we can utilize the useImperativeHandle hook by passing a React ref as a props and configure a reset function to be callable from the Parent component.

// Parent component
import { useRef } from "react";
import "./styles.css";
import { Child } from "./Child";

function App() {
  const childRef = useRef(null);
  return (
    <>
      <div>
        <Child initialCount={0} ref={childRef} />
        <button onClick={() => childRef.current.reset()}>Parent reset</button>
      </div>
    </>
  );
}

export default App;
// Child component
/* eslint-disable react/display-name */
import { forwardRef, useState, useImperativeHandle } from "react";

const childCounterDisplay = {
  display: "flex",
  alignItems: "center",
};

export const Child = forwardRef(({ initialCount }, forwardedRef) => {
  const [count, setCount] = useState(initialCount);
  useImperativeHandle(
    forwardedRef,
    () => {
      return {
        reset: () => {
          console.log("reset function invoked");
          setCount(0);
        },
      };
    },
    []
  );

  return (
    <div ref={forwardedRef}>
      <div>
        Child counter: <span>{count}</span>
      </div>
      <div style={childCounterDisplay}>
        <button onClick={() => setCount((prevCount) => prevCount + 1)}>
          +
        </button>
        <button onClick={() => setCount(0)}>Reset</button>
      </div>
    </div>
  );
});
comments powered by Disqus