- Selector
- Selector를 이용한 비동기 처리
A selector represents a piece of derived state
function selector<T>({
key: string,
get: ({
get: GetRecoilValue
}) => T | Promise<T> | RecoilValue<T>,
set?: (
{
get: GetRecoilValue,
set: SetRecoilState,
reset: ResetRecoilState,
},
newValue: T | DefaultValue,
) => void,
dangerouslyAllowMutability?: boolean,
})
1. key
2. get
3. set
set: ({set}, newValue) =>{ set(hourState, newValue) } // incorrect : cannot align itself
set: ({set}, newValue) =>{ set(minuteState, newValue) } // correct : can align another upstream atom that is writeable RecoilState
const [minute, setMinute] = useRecoilState(minuteState);
=> 공식문서에 나와 있는 설명으로 이해하기 힘들어서 직접 해보기로 함
store.ts
import { atom, selector } from "recoil";
export const minuteState = atom({
key : 'minuteState',
default : 0
})
export const hourState = selector({
key : 'hourState',
get : ({get}) => {
let prev = get(minuteState);
return prev / 60
},
set : ({set}, newValue) => {
const hour = newValue as number * 60;
set(minuteState, hour);
}
});
home.tsx
import React from "react";
import { useRecoilState } from "recoil"
import { hourState, minuteState } from "src/utils/store";
export default function Home () {
const [minute, setMinute] = useRecoilState(minuteState);
const handleSetMinute = (e:React.ChangeEvent<HTMLInputElement>) => {
setMinute(+e.currentTarget.value);
}
const [hour, setHour] = useRecoilState(hourState);
const handleSetHour = (e:React.ChangeEvent<HTMLInputElement>) => {
setHour(+e.currentTarget.value);
}
return (
<>
<input
value={minute}
onChange={handleSetMinute}
type="number"/>
<input
value={hour}
onChange={handleSetHour}
type="number"/>
</>
)
}
- selector를 통해 api 통신을 처리 후 그 selector를 구독하는 모든 컴포넌트에서 selector를 가져와 response data를 받아오는 것이 가능
- 캐싱 기능도 제공
Trello.tsx
import { useEffect, useState } from "react"
import { client, refreshApi } from "../utils/api/api";
import { useRecoilState } from 'recoil';
interface IResponse {
status: string;
message : string;
email : string;
}
interface IToken {
status: string;
message : string;
accessToken : string;
}
export default function Trello() {
const [loading, isLoading] = useState(true);
const [userInfo, setUserInfo] = useRecoilState('');
useEffect(() => {
client.get("/api/refresh")
.then(res => {
const data = res.data as IToken;
client.defaults.headers.common["Authorization"] = `Bearer ${data.accessToken}`
client.get("/api/user")
.then(res => {
let data = res.data as IResponse
setUserInfo(data.email);
})
.catch(err => {
console.log(err);
})
})
},[userInfo]);
return (
{ loading ? (<div>is Loading...</div>) :
(<>
<div>{userInfo}</div>
<button
onClick={handleUserLogout}>logout</button>
</>)
}
)
}
client.ts
import axios from "axios";
export const client = axios.create({
baseURL : "http://localhost:4000",
withCredentials : true,
})
store.ts
import { atom, selector } from "recoil";
import { client } from "./api/api";
interface IToken {
status: string;
message : string;
accessToken : string;
}
interface IResponse {
status: string;
message : string;
email : string;
}
export const userInfo = atom({
key: 'userInfo',
default : ''
})
export const refreshSelector = selector({
key : 'refresh/user',
get : async ({get}) => {
let user = get(userInfo);
await client.get('/api/refresh')
.then( async (res) => {
const data = res.data as IToken;
client.defaults.headers.common["Authorization"] = `Bearer ${data.accessToken}`
await client.get("/api/user")
.then(res => {
let data = res.data as IResponse
user = data["email"];
})
.catch(err => {
console.log(err);
})
});
return user
}
})
Trello.tsx (Selector로 개선)
import { useRecoilValue } from 'recoil';
const Trello = () => {
const userInfo = useRecoilValue(refreshSelector);
return (
<>
<div>{userInfo}</div>
</>
});
export default Trello;
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<RecoilRoot>
<Suspense fallback={<div>Loading ...</div>}>
<App />
</Suspense>
</RecoilRoot>
</React.StrictMode>
);
Trello.tsx
import { useRecoilValueLoadable } from "recoil";
import { client, refreshApi } from "../utils/api/api";
import { refreshSelector } from "../utils/store";
export default function Trello() {
const handleUserLogout = () => {
client.get("/api/logout")
.then(res => {
window.location.replace('/');
})
}
const refreshLoadable = useRecoilValueLoadable(refreshSelector);
switch(refreshLoadable.state) {
case 'hasValue' :
return (
<>
<div>{refreshLoadable.contents}</div>
<button
onClick={handleUserLogout}
>
logout
</button>
</>
);
case 'loading' :
return <div>is Loading...</div>
case 'hasError' :
throw refreshLoadable.contents
}
}
Loadable
ValueLoadable - 비동기 처리가 완료되었을 경우의 객체 -> value 리턴
LoadingLoadable - 비동기 처리 중 일경우의 객체 -> promise 리턴
ErrorLoadable - 비동기 처리 중 에러가 발생했을 경우의 객체 -> error 리턴
const refreshLoadable = useRecoilValueLoadable(refreshSelector);
와 같이 접근할 수 있음
state
와 contents
라는 프로퍼티를 가짐Loadable 객체
- state - hasValue(ValueLoadable), hasError(ErrorLoadable), loading(LoadingLoadable) atom 이나 selector의 상태를 의미하며, 앞의 3가지의 상태를 가짐
- contents - atoms이나 contents의 값을 나타냄, hasValue일 경우 value를, hasError 일 경우 Error 객체, loading일 땐 Promise를 가짐
juno7803 - Recoil 200% 활용하기