Search code examples
javascriptreactjs

Infinite loop when using useDebounce


I making a project in React that I need to fill a form and then send what was answered into my DB, to do so I was wanting to use the hook useDebounce to send the submition after the user stop the interaction. But when I tried to do so it results in a infinite loop and I don't know how to implement it.

This is my code review:

First I have my hook that have all the operations from that entity using GraphQL:

// useRecommendation.js
  ...
// other operations

export const useUpdate = () => {
  const [updateCriteria, { loading, error }] = useMutation(UPDATE_CRITERIA);
  const update = { handle: updateCriteria, loading, error };

  return update;
};

Then I import all operations into a object making like a model:

// Recommendation.js
const Recommendation = {
  all: useGetAll,
  byCategory: useGetByCategory,
  create: useCreate,
  update: useUpdate
}

export default Recommendation

And then I'm importing this entity in my component handler:

export const useRecommendationFormHandler = ({ form }) => {
  const updateRecommendation = Criteria.update();

  const submitRecommendation = async () => {
    try {
      const { data } = await updateRecommendation.handle({
        variables: {
          input: {
            conformity: form.conformity,
            importance: form.importance,
            comment: form.comment,
            conclusion: form.conformity === 0 && form.importance !== null,
          },
          criteria_id: form.id
        }
      });

      return data;
    } catch (error) {
      console.error("Erro ao atualizar a criteria", error);
    }
  };

  return {
    isLoading: updateRecommendation.loading
  }
};

I think it is important to say that the component that I'm trying to use the useDebounce is being rendered within a loop:

      <Grid sx={{ paddingX: 3 }}>
        {forms.length !== 0 && criterias.data.map((data, i) => (
            <RecommendationForm 
              key={data.id} 
              recommendation={data.recommendation.description}
              form={forms.find(f => f.id === data.id)}
              onChange={handleChange}
              index={i}
              onRefetch={onRefetch} 
            />
          ))
        }
      </Grid>

What can I do to send the answers to my db using this hook?

Edit: I was using the useDebounce like this:

  const debouncedForm = useDebounce(form, 1000);
  const updateRecommendation = Criteria.update();

  const submitRecommendation = useCallback(async () => {
    try {
      const { data } = await updateRecommendation.handle({
        variables: {
          input: {
            conformity: debouncedForm.conformity,
            importance: debouncedForm.importance,
            comment: debouncedForm.comment,
            conclusion: debouncedForm.conformity === 0 && debouncedForm.importance !== null,
          },
          criteria_id: debouncedForm.id
        }
      });

      return data;
    } catch (error) {
      console.error("Erro ao atualizar a criteria", error);
    }
  }, [debouncedForm, updateRecommendation]);

  useEffect(() => {
    if (debouncedForm) {
      submitRecommendation()
    }
  }, [debouncedForm, submitRecommendation])

Solution

  • If I had to guess, it would be this:

      const submitRecommendation = // ...
    
      useEffect(() => {
        if (debouncedForm) {
          submitRecommendation()
        }
      }, [debouncedForm, submitRecommendation])
    

    I imagine that submitRecommendation is updating the component state and always returning a new function, which is causing useEffect to be invoked on every render cycle (which it self-triggers). It looks like submitRecommendation shouldn't be affecting when this useEffect should be triggered. If you have the eslint rule on for it, ignore it.

      useEffect(() => {
        if (debouncedForm) {
          submitRecommendation()
        }
      }, [debouncedForm])