Imagine a simple part of a React application that consists of OuterComponent and InnerComponent inside of it. There are instructions these components must follow:
data in OuterComponent changes its value, it must overwrite InnerComponent's localData's current value.localData in InnerComponent changes its value, it must overwrite OuterComponent's data's current value.Here is the code in question:
import React, { useEffect, useState } from "react";
const InnerComponent = ({ data = [], onChange = null }) => {
const [localData, setLocalData] = useState([]);
useEffect(() => {
if (onChange) onChange(localData);
}, [localData, onChange]);
useEffect(() => {
console.log("render");
setLocalData(data);
}, [data]);
return (
<div>
{localData.map((e) => (
<div>{e}</div>
))}
</div>
);
};
const OuterComponent = () => {
const [data, setData] = useState(["alice", "bob"]);
return <InnerComponent data={data} onChange={setData} />;
};
export default function App() {
return (
<div className="App">
<OuterComponent />
</div>
);
}
Since this situation is something which can arise in a lot of cases and thus is typical, are there any existing time-honored ways of refactoring that code into something free from potential infinite rendering cases? I'm also curious whether there is a way if localData is an array while data is an object that has this array in one of its keys which should be monitored by a child component.
Yes, in such cases I'd keep a ref of the previous localData in the InnerComponent and add a guard clause, in the useEffect that calls onChange, to test against current localData e.g.:
const prevLocalData = useRef(localData);
useEffect(() => {
if (JSON.stringify(localData) === JSON.stringify(prevLocalData.current)) {
return undefined;
}
onChange(localData);
}, [localData, onChange])