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>
);
});