[RECOIL] Selector2
학습일 : 2023. 04. 20
저번글에서 했던 방식은
세개의 카테고리를 한번에 렌더링을 했었다.
그 방식 말고
한 번에 하나의 카테고리만 보여주고 싶기 때문에
새로운 state를 만들어 줄 것이다.
이 state의 역할은 사용자가 현재 선택한 카테고리를 저장할 것이다.
select state를 만들어서, 원하는 카테고리의 toDo만 보이게 한번 해보자
그리고 그 state를 이용하면 지금 우리가 toDo를 입력하는 방식도 바꿀 수 있을 것이다.
지금은 toDo를 입력하면 TODO배열로 들어가게 된다.
그게 아닌 지금 내가 원하는 카테고리에 toDo를 등록할 수 있으면 좋겠다.
<atoms.tsx>
카테고리 state를 만들어보자.
atom일 것이고 key로 category,
그리고 기본적으로 카테고리는 TODO 일 것이다.
export const categoryState = atom({
key: "category",
default: "TODO",
});
기본적인 준비는 끝났고 이제 ToDoList.tsx에서
select를 render 해본다
<ToDoList.tsx>
전에 했던 것들을 싹 다 지우고 select 태그 안 option 태그를 적어주고
value를 적어준다.
import { useRecoilValue } from "recoil";
import CreateToDo from "./CreateToDo";
import { toDoSelector } from "./atoms";
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
return (
<div>
<h1>Thorn To Do</h1>
<hr />
<select>
<option value="TODO">TODO</option>
<option value="DOING">DOING</option>
<option value="DONE">DONE</option>
</select>
<CreateToDo />
</div>
);
}
export default ToDoList;
이제 해야 할 것은 select의 변경을 감지하는 것이다.
onInput이라는 함수를 만든다.
이유는 select의 change event는 감지하지 않을 것이기 때문이다.
<ToDoList.tsx>
import { useRecoilValue } from "recoil";
import CreateToDo from "./CreateToDo";
import { toDoSelector } from "./atoms";
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
// 이 함수는 select의 value를 알려준다.
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
// 카테고리를 바꿀 때 어떤 일이 일어나는지 콘솔에 찍어보자
console.log(event.currentTarget.value);
};
return (
<div>
<h1>Thorn To Do</h1>
<hr />
<select onInput={onInput}>
<option value="TODO">TODO</option>
<option value="DOING">DOING</option>
<option value="DONE">DONE</option>
</select>
<CreateToDo />
</div>
);
}
export default ToDoList;
실행결과
이제 이 value를 category state atom과 연결시키는 작업을 해야한다.
어떻게 하면 연결시킬 수 있을까?
현재의 값과, 값을 수정하는 함수를 가져오는 훅을 사용하면 된다!
useRecoilState 훅을 사용한다.
<ToDoList.tsx>
배열을 열고 useRecoilState를 사용
useRecoilState는 atom의 값과 그걸 수정하는 modifier 함수를 반환해준다.
select의 value는 category로 준다.
그런다음 카테고리를 콘솔에 한번 찍어보도록 한다.
*참고*
useRecoilValue는 atom이나 selector의 값만 반환한다.
import { useRecoilValue } from "recoil";
import CreateToDo from "./CreateToDo";
import { toDoSelector } from "./atoms";
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
// useRecoilState 사용하기
// input이 변할 때, setCategory를 호출해 줄 것이다.
const [category, setCategory] = useRecoilState(categoryState)
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value);
};
// 카테고리를 콘솔에 찍어보자
console.log(category);
return (
<div>
<h1>Thorn To Do</h1>
<hr />
<select value={category} onInput={onInput}>
<option value="TODO">TODO</option>
<option value="DOING">DOING</option>
<option value="DONE">DONE</option>
</select>
<CreateToDo />
</div>
);
}
export default ToDoList;
이렇게 되면 연결이 완료된 것이다.
실행결과
카테고리가 정상적으로 찍혀나오는 것을 볼 수 있다.
다음으로, 카테고리가 TODO이면 TODO만,
카테고리가 DOING이면DOING만,
카테고리가 DONE이면 DONE만 render 하도록 해보자.
<ToDoList.tsx>
일단 좀 귀찮은 방법으로 먼저 구현해보도록 한다.
import { useRecoilState, useRecoilValue } from "recoil";
import CreateToDo from "./CreateToDo";
import { categoryState, toDoSelector } from "./atoms";
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
const [category, setCategory] = useRecoilState(categoryState)
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value);
};
console.log(category);
return (
<div>
<h1>Thorn To Do</h1>
<hr />
<select value={category} onInput={onInput}>
<option value="TODO">TODO</option>
<option value="DOING">DOING</option>
<option value="DONE">DONE</option>
</select>
<CreateToDo />
{category === "TODO" && toDo.map((aToDo) => <ToDo key={aToDo.id} {...aToDo} />)}
{category === "DOING" && doing.map((aToDo) => <ToDo key={aToDo.id} {...aToDo} />)}
{category === "DONE" && done.map((aToDo) => <ToDo key={aToDo.id} {...aToDo} />)}
</div>
);
}
export default ToDoList;
실행결과
작동은 잘 되긴 하지만 이 방법이 최선은 아니다.
이 방법 말고 selector를 이용해 보도록 하자.
selector에는 get function이 있다~
get function을 이용하면 selector내부에 여러 atom 들을 가져올 수 있음.
<atom.tsx>
const category = get(categoryState); 를 작성
그리고 어떤 데이터를 반환할지 정할 수 있음
import { atom, selector } from "recoil";
export interface IToDo {
text: string;
id: number;
category: "TODO" | "DOING" | "DONE";
}
// categoryState
export const categoryState = atom({
key: "category",
default: "TODO",
});
export const toDoState = atom<IToDo[]>({
key: "toDo",
default: [],
});
// selector
export const toDoSelector = selector({
key: "toDoSelector",
get: ({ get }) => {
const toDos = get(toDoState);
const category = get(categoryState);
if (category === "TODO")
return toDos.filter((toDo) => toDo.category === "TODO");
if (category === "DOING")
return toDos.filter((toDo) => toDo.category === "DOING");
if (category === "DONE")
return toDos.filter((toDo) => toDo.category === "DONE");
},
});
<ToDoList.tsx>
그리고 ToDoList에 추가
import { useRecoilState, useRecoilValue } from "recoil";
import CreateToDo from "./CreateToDo";
import { categoryState, toDoSelector } from "./atoms";
import ToDo from "./ToDo";
function ToDoList() {
const toDos = useRecoilValue(toDoSelector);
const [category, setCategory] = useRecoilState(categoryState);
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value);
};
console.log(category);
return (
<div>
<h1>Thorn To Do</h1>
<hr />
<select value={category} onInput={onInput}>
<option value="TODO">TODO</option>
<option value="DOING">DOING</option>
<option value="DONE">DONE</option>
</select>
<CreateToDo />
{toDos?.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</div>
);
}
export default ToDoList;
ToDoList 컴포넌트는 굉장히 작아졌다.
toDo 배열 딱 하나만 남았는데,
그건 selector에서 배열을 받아오는 덕분이다.
모든 처리는 selector가 하게 된다.
selector는 toDos와 category를 받고 있다.
그리고 category에 따라서 selector가 각각의 toDo 배열을 반환하게 된다.
실행결과
전과 같이 작동됨을 확인할 수 있음
하지만 코드가 많이 간결해짐.
<atoms.tsx>
조금 더 간결하게
import { atom, selector } from "recoil";
export interface IToDo {
text: string;
id: number;
category: "TODO" | "DOING" | "DONE";
}
// categoryState
export const categoryState = atom({
key: "category",
default: "TODO",
});
export const toDoState = atom<IToDo[]>({
key: "toDo",
default: [],
});
export const toDoSelector = selector({
key: "toDoSelector",
get: ({ get }) => {
const toDos = get(toDoState);
const category = get(categoryState);
return toDos.filter((toDo) => toDo.category === category);
},
});
이것 역시 작동이 잘 됨을 확인 할 수 있다.
이제 각각의 카테고리에는 하나의 리스트만 있게 되었다. 굿