Skip to content
Advertisement

Why is useFetcher causing an re-render infinite loop?

I have an input. On every change to the input, I want to call an API.

Here’s a simplified version of the code:

  // updated by input
  const [urlText, setUrlText] = useState("");

  const fetcher = useFetcher();

  useEffect(() => {
    if (urlText === "" || !fetcher) return;
    fetcher.load(`/api/preview?url=${urlText}`);
  }, [urlText]);

The issue is, when I put urlText inside of the dependencies array, there is an infinite rendering loop, and React claims the issue is I might be updating state inside of the useEffect. However, as far as I can tell, I’m not updating any state inside of the hook, so I’m not sure why an infinite re-render is happening.

Any thoughts?

The fuller version of the code is:

Note: The bug still happens without the debounce, or the useMemo, all of that stuff is roughly irrelevant.

export default function () {
  const { code, higlightedCode } = useLoaderData<API>();

  const [urlText, setUrlText] = useState("");
  const url = useMemo(() => getURL(prefixWithHttps(urlText)), [urlText]);
  const debouncedUrl = useDebounce(url, 250);

  const fetcher = useFetcher();

  useEffect(() => {
    if (url === null || !fetcher) return;
    fetcher.load(`/api/preview?url=${encodeURIComponent(url.toString())}`);
  }, [debouncedUrl]);

  return (
             <input
            type="text"
            placeholder="Paste URL"
            className={clsx(
              "w-full rounded-sm bg-gray-800 text-white text-center placeholder:text-white"
              //"placeholder:text-left text-left"
            )}
            value={urlText}
            onChange={(e) => setUrlText(e.target.value)}
          ></input>
  );
}

Advertisement

Answer

The problem you’re having is that fetcher is updated throughout the fetch process. This is causing your effect to re-run, and since you are calling load again, it is repeating the cycle.

You should be checking fetcher.state to see when to fetch.

useEffect(() => {
  // check to see if you haven't fetched yet
  // and we haven't received the data
  if (fetcher.state === 'idle' && !fetcher.data) {
    fetcher.load(url)
  }
}, [url, fetcher.state, fetcher.data])

https://remix.run/docs/en/v1/api/remix#usefetcher

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement