rsilva
rsilva

Reputation: 270

react custom hook causing infinite loop

I am relatively new to react hooks and I am trying to create this custom hook to handle CRUD operations for my API.

This is the hook file:

import React, { useState, useEffect } from "react";

const useApi = (url, headers = { method: "GET" }, payload = null) => {
  
  const [isLoading, setIsLoading] = useState(true);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);
  const [api, setApi] = useState({});

  const list = async () => {
    try {
      const resp = await fetch(url);
      const data = await resp?.json();
      setApiData(data);
      setIsLoading(false);
    } catch (error) {
      setServerError(error);
    } finally {
      setIsLoading(false);
    }
  };

  const create = async () => {
    try {
      const resp = await fetch(url, (headers = { method: "POST" }), payload);
      const data = await resp?.json();
      setApiData(data);
      setIsLoading(false);
    } catch (error) {
      setServerError(error);
    } finally {
      setIsLoading(false);
    }
  };

  setApi({
    ...api,
    list: list,
    create: create
  });

  return { isLoading, apiData, serverError, api };
};

export default useApi;

However, when I call api.list() in my main component inside a useEffect() hook, I get an infinite loop.

Sample component call:

import { useEffect } from "react";
import useApi from "./useApi";

export default function App() {
  
  const {
    isLoading: loading,
    apiData: students,
    serverError: error,
    api
  } = useApi("https://59f0f160ce72350012bec011.mockapi.io/students");

  console.log(loading, students, error, api);

  useEffect(() => {
    api.list();
  }, [api]);

  return (
    <div className="App">
      <h1>list</h1>
      {loading ? "loading" : students.map((x) => x.name)}
    </div>
  );
}

Here's the sandbox for it: https://codesandbox.io/s/cocky-chebyshev-d9q89?file=/src/App.js:0-492

Can anyone help me understand the issue? Thank you in advance!

Upvotes: 0

Views: 1051

Answers (1)

programmerRaj
programmerRaj

Reputation: 2058

This is what is causing the infinite loop:

setApi({
  ...api,
  list: list,
  create: create
});

You are not supposed to call setState() during a render.

In your case, you don't need to useState for the api object, you can just return it on every render:

return {
  isLoading,
  apiData,
  serverError,
  api: { list, create }
};

Here is a link to the fixed sandbox

Also, another warning: this code will repeatedly call api.list().

useEffect(() => {
  api.list();
}, [api]);

Since api changes on every render, it will repeatedly call api.list().

This is the object that changes on every render:

return { isLoading, apiData, serverError, api };

You can ensure that you only call api.list() one time by using a ref.

import { useRef } from 'react'

// In the component
const gotRef = useRef(false)
useEffect(() => {
  if (!gotRef.current) {
    api.list();
    gotRef.current = true
  }
}, [api]);

Upvotes: 4

Related Questions