1

I'm trying to create a custom useFetch hook and it works in every component except one. In this one component it infinitely loops (but not so much that React stops it). This is the hook:

import { useState, useEffect, SetStateAction, Dispatch } from 'react';
import axios, { AxiosResponse } from 'axios';

const useFetch = (
  method: 'POST' | 'GET',
  url: string,
  object?: any
): [
  AxiosResponse,
  'Loading' | 'Rendered' | 'Error',
  Dispatch<SetStateAction<'Loading' | 'Rendered' | 'Error'>>
] => {
  const [data, setData] = useState<AxiosResponse | null | void>(null);
  const [compState, setCompState] = useState<'Loading' | 'Rendered' | 'Error'>('Loading');

  useEffect(() => {
    let isMounted = true;
    setCompState('Loading');
    if (method === 'GET') {
      // ...
    } else if (
      method === 'POST' &&
      object !== null &&
      !Object.values(object).includes(null)
    ) {
      axios
        .post(url, object)
        .then(res => {
          if (isMounted) {
            console.log(res.data);
            setCompState('Rendered');
            setData(res);
          }
        })
        .catch(err => {
          if (isMounted) {
            console.log(err);
            setCompState('Error');
            setData(err);
          }
        });
    } else return;

    return () => {
      isMounted = false;
    };
  }, [url, object]); 

  return [data as AxiosResponse, compState];
};

export default useFetch;

This is how I'm using it in the component:

  const userId = localStorage.getItem('userId');
  const [orders, compState] = useFetch('POST', '/orders', { userId });

I know part of what is causing the loop; it's because I have the object in the dependency array, and that if I remove it from the array, it stops looping. The problem is that I need the object in the array because, in this case, the object is just the userId, and I want the component to rerender when the user signs in on this page. I assume that something about the userId constant is the cause of this problem.

Before I extracted the custom hook, the constant was in the useEffect and this wasn't a problem, but I'm wondering if theirs a way to fix this that doesn't affect the reusability of the hook.

2 Answers 2

1

You should not pass objects as an array item of second useEffect parameter if you wanting deep property comparison on that object so the best way of doing that if you need a deep comparison on objects properties would be using use-deep-compare-effect as bellow:

import useDeepCompareEffect from 'use-deep-compare-effect';

// Later in our components
useDeepCompareEffect(() => {
    let isMounted = true;
    setCompState('Loading');
    if (method === 'GET') {
      // ...
    } else if (
      method === 'POST' &&
      object !== null &&
      !Object.values(object).includes(null)
    ) {
      axios
        .post(url, object)
        .then(res => {
          if (isMounted) {
            console.log(res.data);
            setCompState('Rendered');
            setData(res);
          }
        })
        .catch(err => {
          if (isMounted) {
            console.log(err);
            setCompState('Error');
            setData(err);
          }
        });
    } else return;

    return () => {
      isMounted = false;
    };
  }, [url, object]); 
Sign up to request clarification or add additional context in comments.

Comments

0

I think you could try as below because useEffect uses shallow comparison, so 2 objects {} and {} are not the same, then it can lead to infinite render.

JSON.stringify(object)

1 Comment

thank you, adding this to the dependency array fixed it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.