6
votes

I'm making a React App and I'm getting this warning. I'm trying to make two API calls when the component mounts:

useEffect(() => {
  getWebsites();
  loadUserRatings();
}, []);

That's why I have an empty array, because I want to call it just once when the component gets mounted. But I'm still getting the warning, how can I fix it?

Those two functions are passed to the component using connect from react redux, the whole component looks like this:

const Wrapper = (props) => {
  const { getWebsites, loadUserRatings } = props;

  useEffect(() => {
    getWebsites();
    loadUserRatings();
  }, []);

  return (
    <>
      <Header />
      <Websites />
      <Sync />
    </>
  );
};
3

3 Answers

8
votes

Since you most probably won't modify your function definitions at runtime, you can add getWebsites, loadUserRatings to the dependencies array.

useEffect(() => {
  getWebsites();
  loadUserRatings();
}, [getWebsites, loadUserRatings]);

Note that the code inside useEffect will run whenever getWebsites, loadUserRatings change. This happens if these two functions aren't memorized.

A good way to prevent that would be to memorize them in the parent component using the useCallback hook and then pass them to the Wrapper component.

See Patrick's answer to know how to memorize using useCallback.

1
votes

You have to add getWebsites and loadUserRatings to the dependencies of useEffect:

useEffect(() => {
  getWebsites();
  loadUserRatings();
}, [getWebsites, loadUserRatings]

All variables defined outside the useEffect hook need to be added, with the stipulation that they're defined in the body of the component or custom hook the useEffect() is called from, or passed down from parameters. Variables defined outside of the component or custom hook don't need to be added to the dependencies list. (memoization) It's one of the lesser known rules of hooks.


Note: (this doesn't work for your scenario as your functions are passed down through the props) You can also wrap your function in a useCallback hook or define the variables you need inside the useEffect hook itself if you don't want to add it to the dependencies of your useEffect hook.

1
votes

In my experience, it's not typical to pass un-memoized functions down via props like this.

// don't do this

<Wrapper
  getWebsites={() => fetchJson('websites').then(setWebsites)}
  loadUserRatings={() => fetchJson('ratings').then(setUserRatings)}
/>

If they are correctly memoized (using a hook like useCallback(), or by being defined outside of any component), then it's safe to pass them to the deps of your useEffect() without any difference in behavior. Here's an example that fixes the scenario above.*

// do this

const fetchJson = (...args) => fetch(...args).then(res => res.json());

const Parent = () => {
  const [websites, setWebsites] = useState([]);
  const [userRatings, setUserRatings] = useState({});

  // useCallback creates a memoized reference
  const getWebsites = useCallback(
    () => fetchJson('websites').then(setWebsites),
    [setWebsites]
  );
  const loadUserRatings = useCallback(
    () => fetchJson('ratings').then(setUserRatings),
    [setUserRatings]
  );

  ...

  <Wrapper
    getWebsites={getWebsites}
    loadUserRatings={loadUserRatings}
  />

* useState() memoizes the dispatch function in its return value, so technically it would be safe to pass [] as the deps to each useCallback() here, but I believe that specifying the dispatch functions as dependencies helps improve clarity by explicitly communicating the author's intent, and there's no disadvantage to passing them.

Ramesh's answer is sufficient for this situation.


If you find that you're stuck with the first scenario, then, as a last resort, you can initialize props into your component's state like this.

const Wrapper = (props) => {
  const [{ getWebsites, loadUserRatings }] = useState(props);

  useEffect(() => {
    getWebsites();
    loadUserRatings();
  }, [getWebsites, loadUserRatings]);

  return (
    <>
      <Header />
      <Websites />
      <Sync />
    </>
  );
};