Recoil의 Selectors
와 React Hook Form
을 학습하며 ToDoList를 만들어 보자.
Atoms
와 함께 Recoil에는 Selectors
라는 개념이 존재한다.Selectors
는 Atoms 상태 값을 동기 또는 비동기 방식을 통해 변환
한다.Recoil의 selectors는 파생 상태(derived state) 또는 상태 변환을 계산하고 관리하기 위한 도구다.
selectors는 기본적으로 하나 이상의 atom 또는 다른 selectors의 상태를 기반으로 새로운 상태 값을 계산하는순수 함수
다.
atom
의 값을 결합하여 새로운 상태 값을 계산할 수 있다.const lengthState = selector({
key: 'lengthState',
get: ({ get }) => {
const text = get(textState);
return text.length;
},
});
selectors
는 비동기 작업을 수행하고 결과를 상태로 반환할 수 있다.const userDataSelector = selector({
key: 'userData',
get: async ({ get }) => {
const response = await fetch('/api/user');
return response.json();
},
});
const resetAllData = selector({
key: 'resetAllData',
set: ({ reset }) => {
reset(dataAtom1);
reset(dataAtom2);
},
});
import { atom, selector } from 'recoil';
export enum FilterKeys {
'TOGO' = 'TOGO',
'BEEN' = 'BEEN',
'LIKE' = 'LIKE',
'DEL' = 'DEL',
}
export interface IToDo {
id: number;
text: string;
filterKey: FilterKeys;
}
export const toDoListFilterState = atom<FilterKeys>({
key: 'toDoListFilterState',
default: FilterKeys.TOGO,
});
export const toDoListState = atom<IToDo[]>({
key: 'toDoListState',
default: JSON.parse(localStorage.getItem('toDoList') || '[]'),
});
export const toDoSelector = selector({
key: 'toDoSelector',
get: ({ get }) => {
const toDos = get(toDoListState);
return [
toDos.filter((toDo) => toDo.filterKey === FilterKeys.TOGO),
toDos.filter((toDo) => toDo.filterKey === FilterKeys.BEEN),
toDos.filter((toDo) => toDo.filterKey === FilterKeys.LIKE),
];
},
});
toDoListState
atom의 전체 할 일 목록을 가져와서, 각 filterKey에 따라 필터링하여 새로운 배열을 반환한다.import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { toDoSelector, toDoListState } from '@/atoms';
import ToDo from './ToDo';
import CreateToDo from './CreateToDo';
export default function ToDoList() {
// 전역 상태로 관리되는 toDoListState 상태값을 불러온다.
const toDoList = useRecoilValue(toDoListState);
// toDoSelector로 필터링된 배열값들을 불러온다.
const [toGo, been, like] = useRecoilValue(toDoSelector);
// toDoList의 최신 상태를 로컬스토리지에 저장한다.
useEffect(() => {
localStorage.setItem('toDoList', JSON.stringify(toDoList));
}, [toDoList]);
return (
<main>
<h2>내가 가고싶은 나라들</h2>
<CreateToDo />
<ul>{toGo?.map((toDo) => <ToDo key={toDo.id} {...toDo} />)}</ul>
<h2>내가 가본 나라들</h2>
<ul>{been?.map((toDo) => <ToDo key={toDo.id} {...toDo} />)}</ul>
<h2>내가 좋아하는 나라들</h2>
<ul>{like?.map((toDo) => <ToDo key={toDo.id} {...toDo} />)}</ul>
</main>
);
}
// CreateToDo 컴포넌트
const CreateToDo = () => {
// toDoList에 새로운 toDo를 추가하기 위해 useSetRecoilState 함수 호출
const setToDoList = useSetRecoilState(toDoListState);
// 새로운 toDo의 필터키 값을 주기위해 초기화 filterKey값 호출
const filterKey = useRecoilValue(toDoListFilterState);
const { register, handleSubmit, setValue, formState } = useForm<IForm>();
const handleValid = ({ toDo }: IForm) => {
setToDoList((prevToDos) => [
...prevToDos,
{ text: toDo, id: Date.now(), filterKey },
]);
setValue('toDo', '');
};
return (
<form onSubmit={handleSubmit(handleValid)}>
<input
{...register('toDo', {
required: 'Please write a To Do',
})}
placeholder="나라 이름"
/>
<button>가자!</button>
<span>{formState.errors.toDo?.message}</span>
</form>
);
};
export default CreateToDo;
// ToDo 컴포넌트
const ToDo = ({ text, filterKey, id }: IToDo) => {
// toDoList의 toDo 상태를 변경하기 위해 useSetRecoilState 함수 호출
const setToDoList = useSetRecoilState(toDoListState);
const onClick = (setFilterKey: IToDo['filterKey']) => {
setToDoList((prevToDos) => {
if (setFilterKey === FilterKeys.DEL) {
return prevToDos.filter((toDo) => toDo.id !== id);
}
return prevToDos.map((toDo) =>
toDo.id === id ? { ...toDo, filterKey: setFilterKey } : toDo,
);
});
};
return (
<li>
<span>{text}</span>
{filterKey == FilterKeys.TOGO && (
<>
<button onClick={() => onClick(FilterKeys.BEEN)}>BEEN</button>
<button onClick={() => onClick(FilterKeys.DEL)}>DEL</button>
</>
)}
{filterKey == FilterKeys.BEEN && (
<>
<button onClick={() => onClick(FilterKeys.TOGO)}>TOGO</button>
<button onClick={() => onClick(FilterKeys.LIKE)}>LIKE</button>
</>
)}
{filterKey == FilterKeys.LIKE && (
<button onClick={() => onClick(FilterKeys.BEEN)}>UNLIKE</button>
)}
</li>
);
};
export default ToDo;
원본 상태를 필터링하여 렌더링한 UI, 원본 상태를 파생하여 생성한 컴포넌트 모두 원본 상태와 의존되어 있다.