0

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.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • 2
    You need to unsderstand how `useEffect` used in different scenario. Try this way . useEffect(() => { if (dataSWR?.data && organizationId) { setData({ id: organizationId, ...dataSWR.data }); console.log({ ...dataSWR.data }); } }, [organizationId]) – Asif vora Nov 09 '21 at 12:15

2 Answers2

5

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]);
}
Asif vora
  • 3,163
  • 3
  • 15
  • 31
  • but is `useSWRImmutable` itself a hook(based on name, it is)? if it is, we will have violation of rules of hooks(https://stackoverflow.com/questions/59070930/is-it-possible-to-use-a-custom-hook-inside-useeffect-in-react has better explanation than official docs, unfortunately). – skyboyer Nov 10 '21 at 22:03
  • So, you can add dependency in `useEffect()`. Example : `useEffect(() => { do something... }, [dataSWR ])` – Asif vora Nov 11 '21 at 05:31
  • Yes, I know. But I thought it's better to put in answer, that is excellent in every other way – skyboyer Nov 11 '21 at 07:27
  • 1
    Update the answer. It's good now? – Asif vora Nov 11 '21 at 08:36
  • Yep! Thank you! – skyboyer Nov 11 '21 at 09:30
0

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;
};
  • Without useMemo, a new object is created (by spread syntax) whenever the hook is called. It doesn't loop if just "data" from SWR are returned, because it return always the same object. – Adam Zálešák Nov 11 '21 at 08:26