Henok Tesfaye
Henok Tesfaye

Reputation: 9560

useEffect array dependency is called in every render when array is not changed

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

Answers (2)

UjinT34
UjinT34

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

Dennis Vash
Dennis Vash

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:

Edit polished-pine-j17om


For class component, use React.PureComponent or implement shouldComponentUpdate.

Upvotes: 5

Related Questions