React custom hook causes infinite loops

any idea why this custom hook with SWR causes an infinite loop?

export const useOrganization = () => {
  const [data, setData] = useState<OrganizationModel | undefined>();
  const { organizationId } = useParams();
  const { data: dataSWR } = useSWRImmutable<
    AxiosResponse<Omit<OrganizationModel, 'id'>>
  >(`organizations/${organizationId}`, api);

  useEffect(() => {
    if (dataSWR?.data && organizationId) {
      setData({ id: organizationId, ...dataSWR.data });
      console.log({ id: organizationId, ...dataSWR.data });
    }
  });
  return data;
};

I need to fetch data from API and add missing id from URL param. If I use setData(dataSWR.data), everything is fine. The problem occurs when setData({...dataSWR.data}) is used -> loop.

Upvotes: 0

Views: 1848

Answers (2)

Asif vora
Asif vora

Reputation: 3359

You need to use useEffect based on the scenario. When dataSWR changed the useEffect call again with new data.

You can add the dataSWR as dependencies argument in useEffect hook.

useEffect(() => { do something... }, [dataSWR])

Example:

export const useOrganization = () => {
  const [data, setData] = useState<OrganizationModel | undefined>();
  const { organizationId } = useParams();
  const { data: dataSWR } = useSWRImmutable<AxiosResponse<Omit<OrganizationModel, 'id'>>>(`organizations/${organizationId}`, API);

  useEffect(() => {
   if (dataSWR?.data && organizationId) {
      setData({ id: organizationId, ...dataSWR.data });
      console.log({ id: organizationId, ...dataSWR.data });
    };
  },[dataSWR]);

  return data;
};

Usage of hook:

const data = useOrganization()

Dependencies argument of useEffect is useEffect(callback, dependencies)

Let's explore side effects and runs:

Not provided: the side-effect runs after every rendering.

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Runs after EVERY rendering
  });  
}

An empty array []: the side-effect runs once after the initial rendering.

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Runs ONCE after initial rendering
  }, []);
}

Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.

import { useEffect, useState } from 'react';

function MyComponent({ prop }) {
  const [state, setState] = useState('');
  useEffect(() => {
    // Runs ONCE after initial rendering
    // and after every rendering ONLY IF `prop` or `state` changes
  }, [prop, state]);
}

Upvotes: 5

I found the solution - useMemo hook:

export const useOrganization = () => {
  const { organizationId } = useParams();
  const { data } = useSWRImmutable<
    AxiosResponse<Omit<OrganizationModel, 'id'>>
  >(`organizations/${organizationId}`, api);

  const result = useMemo(() => {
    if (data && organizationId) {
      return { id: organizationId, ...data.data };
    }
  }, [data, organizationId]);
  console.log('useOrganization');
  return result;
};

Upvotes: 0

Related Questions