Recoil과 Form을 이용한 리스트만들기
1.atom.ts
import { atom, selector } from 'recoil';
//TodoData에 사용할 interface
export interface IToDo {
text: string;
id: number;
category: 'NONE' | 'VISIT' | 'LIKE';
}
export const TodoData = atom<IToDo[]>({
key: 'ToDo',
default: [],
});
//TodoData의 원본값을 변경하지 않고 원하는 형태의 값들만 리턴한다.
export const TodoSelector = selector({
key: 'TodoSelector',
get: ({ get }) => {
const getData = get(TodoData);
//리턴된 값들을 []배열안에 객체형식으로 담아준다.
return [
getData.filter((i) => i.category === 'NONE'),
getData.filter((i) => i.category === 'VISIT'),
getData.filter((i) => i.category === 'LIKE'),
];
},
});
2.CreateTodo.tsx
interface ITodoData {
[key: string]: string;
}
export default function CreateTodo() {
//TodoData의 값 변경위해 useSetRecoilState 사용
const setTodo = useSetRecoilState(TodoData);
//Form에서 사용할것들 useForm사용해 꺼내준다
const {
register,
handleSubmit,
formState: { errors },
setValue,
} = useForm<ITodoData>();
//Form의 onSubmit에 들어갈 이벤트
const onValid = (data: ITodoData) => {
//input값을 빈값으로 초기화
setValue('todo', '');
//input값을 객체 형식으로 담아 TodoData에 넣어준다
//TodoData의 기본값은 배열이니 배열에 담고 ...pre를 사용해 기존 배열을 복사한다.
setTodo((pre) => [
{ text: data?.todo, id: Date.now(), category: 'NONE' },
...pre,
]);
};
return (
<>
<Form onSubmit={handleSubmit(onValid)}>
<input
type="text"
placeholder="이름"
{...register('todo', {
required: '😠 required!',
})}
/>
//input이 빈값일때 required에 해당하는 에러 메시지 노출
<span>{errors?.todo?.message}</span>
<button>가자</button>
</Form>
</>
);
}
import { IToDo, TodoData } from '../atom';
export default function Todo({ text, id, category }: IToDo) {
//useSetRecoilState를 사용하여 TodoData의 state를 바꿀 변수 선언
const setToDos = useSetRecoilState(TodoData);
//삭제될 Todo Event
const onDelete = () => {
//filter를 사용하여 기존 배열에 있는 id와 현재 id 비교하여 같지 않는것들만 남김
setToDos((prev) => prev.filter((todo) => todo.id !== id));
};
//button클릭시 category바뀔 이벤트 생성
//button클릭시 들어오는 인자를 받는다(newCategory)
const onChangeCategory = (newCategory: IToDo['category']) => {
setToDos((prev) => {
//기존 data 배열의 id와 현재 Todo의 id가 일치하면 맞는 index반환
const targetIdx = prev.findIndex((todo) => todo.id === id);
//업데이트될 함수
const updatedTodo = { text, id, category: newCategory };
//기존 배열의 앞부분과 뒷부분 사이에 updatedTodo를 넣으면 배열의 순서가 바뀌지 않고
//업데이트 된 부분만 변경된다.
return [
...prev.slice(0, targetIdx),
updatedTodo,
...prev.slice(targetIdx + 1),
];
});
};
//카테고리별로 button개수&내용 변경
return (
<li>
<span>{text}</span>
{category === 'NONE' && (
<>
<button onClick={() => onChangeCategory('VISIT')}>✅</button>
<button onClick={onDelete}>🗑️</button>
</>
)}
{category === 'VISIT' && (
<>
<button onClick={() => onChangeCategory('LIKE')}>👍🏻</button>
<button onClick={() => onChangeCategory('NONE')}>❌</button>
</>
)}
{category === 'LIKE' && (
<button onClick={() => onChangeCategory('VISIT')}>👎🏻</button>
)}
</li>
);
}
export default function TodoList() {
//TodoSelector에서 나눠 놓은 배열을 순서대로 이름을 지정해준다.
const [none, visit, like] = useRecoilValue(TodoSelector);
return (
<>
<h1>내가 가고싶은 나라들</h1>
<CreateTodo />
<ul>
{none.map((i) => (
<Todo key={i.id} {...i} />
))}
</ul>
<h1>내가 가본 나라들</h1>
<ul>
{visit.map((i) => (
<Todo key={i.id} {...i} />
))}
</ul>
<h1>내가 좋아하는 나라들</h1>
<ul>
{like.map((i) => (
<Todo key={i.id} {...i} />
))}
</ul>
</>
);
}

recoil의 selector기능이 내가 원하는 data들 값만 정리해서 사용하기 좋았고, Form을 사용하니 useForm하나만 선언해서 여러 기능들을 꺼내 쓸 수 있어서 좋았다.
이번 코드 챌린지를 하면서 단순한 ToDoList가 아닌 내가 지정한 카테고리 별로 이동을 하는부분에서 꽤 애를 먹었다.
버튼의 내용,기능,개수 다른부분에서도 어떻게 구현해야할지 머리가 아팠지만 구글링과 주변의 조언을 통해 나름 간단히 해결한거 같아 뿌듯하다.