[Recoil] atom의 default 값을 서버 데이터로 사용할 때 주의할 점 (Warning: Can't perform a React state update on a component that hasn't mounted yet.)

@eunjios·2024년 3월 8일
0
post-thumbnail

문제 상황

리액트 앱 테스트를 공부하던 나 . . 더미 데이터로 개발하고 있었지만 비동기 테스트 부분을 배울 차례가 되어 더미 데이터가 아닌 서버 측 데이터를 fetch 하도록 코드 변경이 필요했다.

빠른 개발을 위해 가장 간단한 방법을 선택했다. Recoil 은 상당히 리액트스러워서 default 값만 서버 데이터로 변경하면 코드 변경이 거의 없다. 솔루션은 다음과 같다.

(기존) 더미 데이터 사용

실제로는 useState 를 사용했지만 비교를 위해 Recoil로 구현해 봤다.

import { atom } from 'recoil';

export const myAtom = atom({
  key: 'myAtom',
  default: DUMMY_DATA,
});

(변경) API 데이터 사용

import { atom, selector } from 'recoil';
import { getData } from '../api';

export const mySelector = selector({
  key: 'mySelector',
  get: async () => {
    return await getData();
  },
});

export const myAtom = atom({
  key: 'myAtom',
  default: mySelector,
});

위처럼 비동기 데이터를 atom의 default 값으로 설정할 수 있었다. 하지만 다음과 같은 에러가 발생했다.

Warning: Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously later calls tries to update the component. Move this work to useEffect instead.

마운트 되지 않은 컴포넌트의 state 를 업데이트 할 수 없다는 문제였다. 즉 useEffect를 사용해서 컴포넌트가 마운트 되면 그 후에 해당 state를 업데이트 해주라는 것이다. 그러면 Recoil 안 썼지 ..

문제 해결

<React.Suspense> 를 사용하자!

해당 문제는 공식 문서에도 잘 나와 있는 내용이라 쉽게 해결할 수 있었는데 비동기 데이터를 사용하는 경우 React.Suspense 를 사용해 fallback 을 지정해 주면 된다.

비동기 데이터를 사용하는 컴포넌트

export default function MyComponent() {
  const [data, setData] = useRecoilState(myAtom); 
  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.text}</p>
      <button>{() => setData({ title: 'new', text: 'new' })}
        새로운 데이터로 변경
      </button>
    </div>
  );
}

위 컴포넌트를 감싸는 컴포넌트

export default function MyApp() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={null}>
        <MyComponent />
      </React.Suspense>
    </RecoilRoot>
  );
}

비동기 데이터가 도착하기 전까지 아무것도 보여주고 싶지 않아서 fallback 을 null 로 지정하였는데, 보여주고 싶은 특정 컴포넌트가 있다면 해당 컴포넌트로 지정하면 된다.

참고로 selector 내부에서 발생하는 에러를 catch 하고 싶다면 다음과 같이 ErrorBoundary로 컴포넌트를 감싸면 된다.

export default function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary fallback={<p>something went wrong..</p}>
        <React.Suspense fallback={null}>
          <MyComponent />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

실제 코드 확인

위 코드는 설명을 위해 임의로 작성하였는데, 실제 코드 및 관련 커밋이 궁금하다면 아래 링크를 확인해 주십쇼

🔗 refactor: dummy data to server data

References

profile
growth

0개의 댓글