1
import api from "api";
import { Loader } from "components/base";
import { useReducer } from "react";
import { useQuery } from "react-query";

function reducer(state, { type, payload }) {
  switch (type) {
    case "init":
      return state.concat(payload);
    default:
      throw new Error();
  }
}

function Table() {
  const [workers, dispatch] = useReducer(reducer, []);

  const fetchWorkers = async () => {
    const workersData = await api.index();
    return workersData;
  };

  const { status, data, error } = useQuery("", fetchWorkers);

  switch (status) {
    case "loading":
      return <Loader />;

    case "error":
      return <p className="text-red-600">{error.message}</p>;

    case "success":
      dispatch({ type: "init", payload: data });
      return <p>Success!</p>;

    default:
      return <p></p>;
  }
}

export default Table;

The above code causes infinite re-renders? Why?

Once useQuery communicates that status === "success", why can't I just dispatch and initialize my data? How else should I be doing this instead?

I removed useQuery and just did with a useEffect without any issue - the data came back and I dispatched.

What is different here with useQuery?

CodeFinity
  • 1,142
  • 2
  • 19
  • 19

1 Answers1

4

Your code is causing infinite re-renders because you're calling dispatch every render after the data has loaded:

case "success":
  // since the switch() statement runs every render, as long as it's "success"
  // we'll call dispatch, update the reducer, and then force a re-render
  // thus causing a loop
  dispatch({ type: "init", payload: data });
  return <p>Success!</p>;

There are two ways you can avoid this.

  1. Don't put the data into your reducer. It doesn't look like you have any reason to do that in your sample code, and in case the data is needed in more places, you could potentially just call useQuery there as well (I'm not terribly familiar with the library but I imagine they have some caching strategy).

  2. If you really need the data in the reducer, do the dispatch within an effect so it only runs the moment status changes to success:

    React.useEffect(() => {
      if (status === 'success') {
        dispatch({ type: 'init', payload: data });
      },
    }, [status]); // since `status` is in the dependency, this effect only runs when its value changes
    

General lesson is try to avoid calling state-changing functions directly within the render body of your component--prefer effects and callbacks.

y2bd
  • 5,783
  • 1
  • 22
  • 25
  • 1
    Yes. I think `react-query` is meant to be used when we are doing a lot of `async` stuff in real-time with the server. It may not be appropriate here where I am just playing around with local state updates on the data - there is no real CRUD back-end. – CodeFinity Mar 01 '21 at 01:43