Cleanup setTimeout inside useffect async function

Viewed 84

I'm developing an autosave component in React.

My logic is whenever the "bestOf" changes, I check with an async fetch function if the data is equal to the data that is stored in my database.

  • If it is equal, I set my state to saved
  • If it is not equal, I set my state to unsaved and then I want to wait 5 seconds before calling my handleSave function

I am now trying to cleanup this timeout in order to not call this function multiple times if multiple modifications to the bestOf occurs but the useEffect cleanup function doesn't work in my case.

Here is my try with clearTimeout

    useEffect(() => {
        let timeout;
        const compareSave = async () => {
            await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`)
                .then((res) => res.json())
                .then((data) => {
                    if (JSON.stringify(data) === JSON.stringify(bestOf)) {
                        return setSave("saved");
                    } else {
                        setSave("unsaved");
                        timeout = setTimeout(() => {
                            handleSave();
                        }, 5000);
                    }
                })
                .catch((err) => console.log(err));
        };

        compareSave();

        return () => {
            clearTimeout(timeout);
        };
    }, [bestOf]);

And here is another try with AbortController

    useEffect(() => {
        const controller = new AbortController();
        const compareSave = async () => {
            await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`, {
                signal: controller.signal,
            })
                .then((res) => res.json())
                .then((data) => {
                    if (JSON.stringify(data) === JSON.stringify(bestOf)) {
                        return setSave("saved");
                    } else {
                        setSave("unsaved");
                        setTimeout(() => {
                            handleSave();
                        }, 5000);
                    }
                })
                .catch((err) => console.log(err));
        };

        compareSave();

        return () => {
            controller.abort()
        };
    }, [bestOf]);

Both solutions doesn't work and the timeout function is executed multiple time for each modification. Any suggestions ?

1 Answers

After a lot of tries i finally found that I need to add a flag to ignore the stuff after fetch in the timeout for if bestOf changes.

Otherwise because it is async, the timeout would be set after I have cleared it.


useEffect(() => {
  let shouldIgnore = false;
  let timeout;

  const compareSave = async () => {
    await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`)
      .then((res) => res.json())
      .then((data) => {
        if (shouldIgnore) {
          return;
        }

        if (JSON.stringify(data) === JSON.stringify(bestOf)) {
          return setSave('saved');
        } else {
          setSave('unsaved');
          timeout = setTimeout(() => {
            handleSave();
          }, 5000);
        }
      })
      .catch((err) => console.log(err));
  };

  compareSave();

  return () => {
    shouldIgnore = true;
    clearTimeout(timeout);
  };
}, [bestOf]);

Related