Skip to main content

Command Palette

Search for a command to run...

[ 살펴보기 ] Recoil - Atom Effects

Updated
3 min readView as Markdown
[ 살펴보기 ] Recoil - Atom Effects
C

A developer living in Busan, Korea

Atom effects는 atom에 변경이 발생했을 때를 catch하여 변경에 다른 side effect를 발생시킬 수 있다 ( React useEffect와 유사하다 )

기본적인 형태는 다음과 같다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: userListDefaultState,
  effects: [
    () => {
      console.log(" ::: userListState effect :::");
    },
  ],
});

만약 effects array에 여러개의 effect function을 전달하면 전달한 순서대로 실행된다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: userListDefaultState,
  effects: [
    () => {
      console.log(" ::: userListState effect :::");
    },
    () => {
      console.log(" ::: userListState effect 2 :::");
    },
  ],
});

setSelf

effect function의 setSelf를 통해 현재 atom의 값을 업데이트 할 수 있다. 아래와 같이 설정하면 effect가 실행될 때 atom의 value가 { name:"jack", age:23 }으로 업데이트 된다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: [],
  effects: [
    ({ setSelf }) => {
      setSelf([{ name: "jack", age: 23 }]);
    },
  ],
});

OnSet

onSet을 통해 atom에 변경이 발생했을 때 마다 side effect를 처리한다. onSet에서 아래의 예제와 같이 업데이트 된 새로운 value, 업데이트 되기 전 value, 그리고 atom의 변경사항이 reset 함수를 통해 발생한 것이지 체크할 수 있다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: [{ name: "jack", age: 23 }],
  effects: [
    ({ onSet }) => {
      onSet((newVal, oldValue, isReset) => {
        console.log({ newVal, oldValue, isReset });
      });
    },
  ],
});

다만 setSelf를 통해 update가 발생하면 onSet는 실행되지 않는다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: [{ name: "", age: 0 }],
  effects: [
    ({ onSet, setSelf }) => {
      setSelf([{ name: "jake2", age: 20 }]);
      /*
        위의 setSelf로 인해 atom이 업데이트 되어도 
        아래 onSet은 실행되지 않는다
      */
      onSet((newVal, oldValue, isReset) => {
        console.log({ newVal, oldValue, isReset });
      });
    },
  ],
});

LocalStorage와 연결하여 사용하기

다음과 같이 atom effects를 통해 브라우저의 localStorage의 특정 값과 atom을 연결하여 사용할 수 있다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: [{ name: "", age: 0 }],
  effects: [
    ({ onSet, setSelf, trigger }) => {
      const savedValue = localStorage.getItem("myInfo");
      if (savedValue != null) {
        setSelf(JSON.parse(savedValue));
      }
      onSet((newValue, oldValue, isReset) => {
        isReset
          ? localStorage.removeItem("myInfo")
          : localStorage.setItem("myInfo", JSON.stringify(newValue));
      });
    },
  ],
});

위의 예제를 살펴보면 effect가 처음 발생할 때 localStorage에서 값을 가져와 값이 있다면 현재 atom에 업데이트 한다

그리고 그 이후에 atom의 값에 변경이 있다면 onSet에서 변경사항을 catch해 localStorage에 저장하거나 reset이 발생한다면 해당 localStorage 값을 초기화 한다

비동기 처리와 Suspense

Atom effects에서 비동기 처리를 통해 data를 업데이트 해야 한다면 다음과 같이 처리할 수 있을 것이다.

첫 번째 방법은 setSelf에 Promise객체 자체를 전달하는 것 것이다.

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: [{ name: "", age: 0 }],
  effects: [
    ({ onSet, setSelf, trigger }) => {
      const getAsyncData = async () => {
        const result = await apiGetTestData();
        return result.data;
      };
      setSelf(getAsyncData());
      onSet((newValue, oldValue, isReset) => {
        console.log({ newValue, oldValue, isReset });
      });
    },
  ],
});

만약 위의 atom을 사용하는 component 상단에 Suspense로 처리가 되어 있다면 sefSelf에 전달된 promise가 resolve되게 전까지는 Suspense의 fallback으로 대체된다 ( NextJS와 사용할 때 dev 환경 또는 build시 pending 상태에서 resolve되지 않는 현상이 보이는 것 같아 주의가 필요 )

반면 다음과 같이 비동기 처리를 마치고 최종 값만 setSelf에 전달한다면 Suspense는 사용되지 않고 우선 detault에 설정된 값이 우선 사용되며 비동기 처리가 모두 끝나고 난 뒤에 atom 값이 업데이트 된다

export const userListState = atom<UserAtom[]>({
  key: "userListState",
  default: [{ name: "", age: 0 }],
  effects: [
    ({ onSet, setSelf, trigger }) => {
      const getAsyncData = async () => {
        const result = await apiGetTestData();
        setSelf(result.data);
      };
      getAsyncData();
      onSet((newValue, oldValue, isReset) => {
        console.log({ newValue, oldValue, isReset });
      });
    },
  ],
});

More from this blog

[ 살펴보기 ] TypeORM - Transactions, Migration

Transation Database 종류에 따라 detail한 부분은 차이점이 조금씩 있겠지만 각 sql statement는 개별적인 transaction block을 통해 실행되며 Database 설정에 따라 sql statement의 실행 결과가 자동으로 commit되어 영구히 적용되거나 commit을 직접 실행하기 전까지는 영구히 적용되지 않을 수 있다. 대부분의 경우 default로 sql statement 실행 결과가 자동으로 comm...

Feb 9, 20256 min read
[ 살펴보기 ] TypeORM - Transactions, Migration

Dev Diary

184 posts