I have a code similar to this (I know this code is stupid, but it's to get an example)
import { useState, useEffect } from "react";
const MyComponent = (props) => {
const { names, indexes, onIndexesChange } = props;
const [sortDir, setSortDir] = useState(1);
useEffect(() => {
const newIndexes = [0, 1, 2];
newIndexes.sort((i1, i2) => {
return sortDir * names[i1].localeCompare(names[i2]);
});
onIndexesChange(newIndexes);
}, [sortDir, onIndexesChange]);
return (
<p>
<button onClick={() => setSortDir(-sortDir)}>Click</button>
<br />
{indexes.map((index) => names[index])}
</p>
);
};
export default function App() {
const names = ["Newton", "Einstein", "Pascal"];
const [indexes, setIndexes] = useState([0, 1, 2]);
// in real code, indexes is shared between multiple components,
// which it is declared above and not in MyComponent
return (
<div className="App">
<MyComponent
names={names}
indexes={indexes}
onIndexesChange={setIndexes}
/>
</div>
);
}
The above throws an expected warning
React Hook useEffect has a missing dependency: 'names'. Either include it or remove the dependency array.
I could add names
to the array of dependencies but then I get an infinite loop using React 18.2.0 (I do not get one using React 18.0.0) since names is redefined on each render.
How can I get this working regardless of how names
is declared (e.g., as a state or as a const variable)?
Sandbox code: https://codesandbox.io/s/clever-breeze-p1nmx7?file=/src/App.js:199-233
CodePudding user response:
As I said In the previous comment, you can use UseMemo to avoid the re-render.
const C2 = () => {
const names = useMemo(() => ["a", "b", "c"], []);
return <MyComponent names={names} />
}
CodePudding user response:
There are a few ways:
useMemo (for state variables)
const names = useMemo(() => ["a", "b", "c"], []);
Define names outside the component (for non-state variables)
const names = ["a", "b", "c"];
const C2 = () => {
return <MyComponent names={names} />
}
useRef (for non-state variables)
const C2 = () => {
const names = useRef(["a", "b", "c"]);
return <MyComponent names={names.current} />
}
If your goal is to ensure that regardless of how the prop gets to your component, that it does not cause your library component to infinitely re-render, then you might want to consider React.memo
(ymmv):
const MyComponent = memo((props: { names: string[] }) => {
const { names } = props;
const [index, setIndex] = useState(0);
const [welcome, setWelcome] = useState("");
useEffect(() => {
setWelcome(`Welcome ${names[index]}`);
}, [index]);
return (
<span>
<Button onClick={() => setIndex((index 1) % names.length)}>Next</Button>
{welcome}
</span>
);
}, ({names: prevNames}, {names: newNames}) => {
return prevNames === newNames || prevNames.join(",") === newNames.join(",");
});