0

I know this question gets asked a lot, but from the answers I could find I saw:

  • either no cleanup function in useEffect (so not my use case)
  • people not using dependencies at all (and a simple [] dependency would suffice)

So, here's my code:

import { useEffect, useState } from "react";
import { Button } from "@material-ui/core";
import { CircularProgress } from "@material-ui/core";

export default function App() {
  const [tasks, setTasks] = useState<string[]>([]);

  useEffect(() => {
    const getTasks = () => {
      setTasks(["1", "2", "3"]);
    };
    getTasks();
    return () => {
      setTasks([]);
    };
  }, [tasks]);

  if (tasks.length === 0) {
    return <CircularProgress />;
  }

  return (
    <div>
      {JSON.stringify(tasks)}
      <Button onClick={() => setTasks([])}>Reset tasks data</Button>
    </div>
  );
}

CodeSandbox link: here. I removed the tasks from useEffect dependencies, because otherwise there's an infinite loop and the browser tab may crash.

Let me explain what I want to achieve. This is just a snippet trying to mimic what I'm working on. I have a page with a table. This table is created by pulling some data from an API. So, the page loads, a table gets rendered. Then, under a table I have an "Add task" button. This brings up a little form to the front, where a new task can be created. I pass an onSubmit method to it, so when a user clicks "Create", the POST request is made. And then I want the table to be updated.

To do this, I set the tasks to [], which are state, so component gets re-rendered, useEffect kicks in and re-downloads the tasks, which will contain the newly created one. And it works, but only if I don't use a cleanup function in useEffect. If I do, I enter an infinite loop (because of the tasks dependency I guess).

As I don't want to put axios calls in the example, this is the analogy:

  • getTasks in my app makes the API call
  • the button from the example is the button that opens a form to create a new task
  • didn't want to add a table to the example, so just to show the data, I JSON.stringify() it

Anyway, to the question: I know I need the dependency to make the page refresh when I make the changes (in the example - click the button). How can I achieve what I want (re-render the page after the change to tasks is made) and retain the cleanup function in useEffect? (because if I remove it, everything works)

dabljues
  • 1,663
  • 3
  • 14
  • 30
  • You just need to remove the `tasks` from the dependency array (it's not referenced in the useEffect and so doesn't need to be there). – pilchard Apr 10 '22 at 21:56
  • @pilchard won't solve OP issue, they want the effect to run also when `tasks` is set to `[]` – nickfla1 Apr 10 '22 at 21:58
  • 1
    it will always be set to `[]` on each unmount, so fetch on every render? better to just set a flag, or call getTasks in the changeHandler. possibly see: [How to refetch data when field change with react hooks](https://stackoverflow.com/questions/59975315/how-to-refetch-data-when-field-change-with-react-hooks) or look at something like [SWR](https://swr.vercel.app/) which was made just for this – pilchard Apr 10 '22 at 22:00
  • @pilchard They also have a `setTasks([])` inside the Button's `onPress` callback – nickfla1 Apr 10 '22 at 22:01
  • As for resetting - to let it re-render and update itself using `useEffect`. As for resetting the state in the cleanup function - isn't that what they're for? – dabljues Apr 10 '22 at 22:08
  • 1
    Why was this closed, propoed duplicate is clearly not OP's main problem – nickfla1 Apr 10 '22 at 22:10
  • Indeed, I voted to reopen. The problem here is clearly not the usual infinite loop we get every day here. The solution here is to not misuse the single state field (tasks) for both "state of data" and "state of fetching" - there should be one more state field (let's call it status) to indicate "fetching/success/error" and then the callback on the button should trigger refetching of data while setting these flags. SWR was offered, you can use React Query as well. Or you can try implement it yourself first to get a deeper understanding of what it does before using a library. – Jakub Kotrs Apr 10 '22 at 22:20
  • 1
    @nickfla1 The `useEffect` hook unconditionally updates the very state it uses as a dependency, which the duplicate QnA covers. As pilchard also correctly points out, `tasks` isn't even referenced in the hook callback and isn't a dependency. It seems OP has a fundamental misunderstanding for how the `useEffect` hook and React component lifecycle should work. – Drew Reese Apr 10 '22 at 22:21
  • @DrewReese Are we ignoring the rest of the answer? The infinite loop is definetly caused by the `tasks` dependency but getting rid of it isn't OP final goal, removing it won't solve their issues, just one of them and in the wrong way. – nickfla1 Apr 10 '22 at 22:23
  • 2
    As far as I can tell, the `getTasks` function should be moved outside the `useEffect` hook as a regular function, and the `useEffect` hook should call it once to initialize any state, and then any buttons that want/need to trigger another fetch should call `getTasks` instead of resetting state and trying to make the `useEffect` hook pull this odd double-duty. – Drew Reese Apr 10 '22 at 22:24
  • 1
    @DrewReese that would have been a good answer – nickfla1 Apr 10 '22 at 22:24
  • 1
    I never said there weren't more issues in the code. The title is about the infinite loop based on dependencies. Fixing a dependency issue fixes a render looping issue. – Drew Reese Apr 10 '22 at 22:25
  • 1
    Okay, I moved the `getTasks` out from `useEffect` as @DrewReese suggested and I use it there + in the button `onClick()` function. And it works. You're right, I went too lazy with trying to use `useEffect` for both fetching and re-rendering the component. @nickfla1, thanks for fighting for the question, I am a React newbie/casual so I don't claim any knowledge at all, but the question linked as the one that should answer mine - didn't, at least for me :) My issue is resolved, I'll definitely take a look at SWR – dabljues Apr 10 '22 at 22:32
  • And I checked SWR - holy moly this is awesome. I am basically now running through my repo and removing `useEffect`s responsible only for fetching the data and replacing them with `useSWR`. Not even mentioning the caching and auto-updates! – dabljues Apr 10 '22 at 23:25

0 Answers0