recoil에서는 비동기적인 API호출도 가능하다. recoil은 데이터 플로우 그래프를 통해 상태를 매핑하는 방법과 파생된 상태를 react component에 제공한다.
이러한 플로우에 속한 함수들은 물론 비동기가 될 수도 있다. 이는 비동기 함수들을 동기 react 컴포넌트에서 사용하게 쉽게 만들어준다.
const currentUserNameQuery = selector({
key: 'currentUserNameQuery',
get: async ({ get }) => {
const response = await axios.get('/api/username?id=1');
return response.name;
},
});
하지만, react의 렌더 함수가 동기인데 promise가 resolve 되기 전에 무엇을 렌더 할 수 있을까? recoil은 보류중인 데이터를 다루기 위해 React Suspense와 함께 동작하도록 디자인되어 있다. 컴포넌트를 Suspense의 경계로 감싸는 것으로 아직 보류중인 하위 항목들을 잡아내고 대체하기 위한 UI를 렌더하게 해준다.
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo /> {/* 비동기 selector를 이용하는 컴포넌트를 Suspense 안에 넣어준다. */}
</React.Suspense>
하지만 대부분의 비동기로 호출하는 API는 호출할 데이터의 특정 인자를 쿼리로 호출한다.
recoil은 인자를 받아 selector를 호출할 수 있도록 하는 selectorFamily를 제공한다. 아래와 같이 사용할 수 있다.
export const currentUserQuery = selectorFamily({
key: 'currentUserQuery',
// 인자를 받아올 수 있도록 중첩 함수를 사용
get: (id: string) => async () => {
// axios를 통해 비동기 api 호출
const res = await axios.get(`/api/username?id=${id}`);
return res.data.username;
}
});
<br>
// 더미 데이터
const tableOfUsers: User[] = [
{
username: 'Grag'
},
{
username: 'Alex'
},
{
username: 'Milick'
}
];
export const handler = [
rest.get('/api/username', async (req, res, ctx) => {
const index = Number(req.url.searchParams.get('id')); // 쿼리에 있는 id 변수를 받아옴
return res(
ctx.json({
// 받아온 쿼리는 1이고, 배열의 1번째에는 Alex가 있음
username: tableOfUsers[Number(index)]
})
);
}),
];
const SynchronousExample: FC = () => {
const { username } = useRecoilValue<User>(currentUserQuery('1')); // 인자로 전달
return (
<div>
// 비동기 api가 로딩중일 때는 fallback의 element가 렌더된다.
<React.Suspense fallback={<div>Loading...</div>}>
user : {username}
</React.Suspense>
</div>
);
};
동기 로직을 처리하는데 있어서 selectorFamily의 이점은 코드의 구조를 더 잘 조직화하고 관리하기 쉽게 만들어주며, 효율적인 리소스 사용과 유연한 상태 관리를 가능하게 한다. 이를 활용하면 상태 관리 라이브러리에서도 데이터 패칭을 손쉽게 다룰 수 있다.