Home > Blockchain >  Use props as dependencies in useEffect
Use props as dependencies in useEffect

Time:08-12

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(",");
});
  • Related