Reputation: 9560
import React, { useState, useEffect } from "react";
export default function App() {
const [columns, setColumns] = useState([
{ name: "a" },
{ name: "b" },
{ name: "c" }
]);
const [isOpen, setIsOpen] = useState(false);
const addName = () => setColumns([...columns, { name: "r" }]);
const toggleOpen = () => setIsOpen(!isOpen);
return (
<>
<List columns={columns} />
<button onClick={toggleOpen}>Toggle</button>
<button onClick={addName}>Add</button>
<p>{isOpen.toString()}</p>
</>
);
}
const List = ({ columns }) => {
const names = columns.map(col => col.name);
useEffect(() => {
console.log("Names is changed to: ", names);
}, [names]);
return <p>{names.join(" ")}</p>;
};
Names is changed to:
is called, when isOpen
state is changed in App
component.
I want the console.log
to be executed only when names array is changed.
I think in List
component, it is creating a new array whenever render, so that the previous array and the new array are not equal.
Upvotes: 7
Views: 7237
Reputation: 4987
const names = columns.map(col => col.name);
Creates a new array every time and useEffect
thinks that dependencies have changed.
To avoid that either pass names
directly to useEffect
:
useEffect(() => {
console.log("Names is changed to: ", names);
}, names);
Or useMemo
to get the same array object:
const names = useMemo(() => columns.map(
col => col.name
), [columns]);
Upvotes: 4
Reputation: 53884
You should memoize the component so it will render only on props change (or if comparison function passed as 2nd argument).
Currently, List
rendered due to its parent App
render.
const List = ({ columns }) => {
const names = columns.map((col) => col.name);
useEffect(() => {
console.log("Names is changed to: ", names);
}, [names]);
return <p>{names.join(" ")}</p>;
};
const MemoList = React.memo(List);
export default function App() {
return (
<>
<MemoList columns={columns} />
</>
);
}
See working example:
For class component, use React.PureComponent
or implement shouldComponentUpdate
.
Upvotes: 5