한 걸음씩
[React] To Do 본문
▶ atom.tsx
- Recoil의 atom상태와 Selector를 정의하는 파일
- Categories 열거형: 'TO_DO', 'DOING', 'DONE'의 카테고리를 나타내는 상수들을 정의한다.
- IToDo 인터페이스: 할 일에 대한 정보를 담는 객체의 형식을 정의한다.
- toDoState: Recoil의 atom으로서, 할 일 정보를 담는 배열을 관리한다.
- categoryState: 현재 선택된 카테고리를 관리한다.
- toDoSelector: 선택된 카테고리에 따라 필터링된 할 일 목록을 반환하는 Selector를 정의한다.
// atom.tsx
import { atom, selector } from 'recoil'
// type categories = 'TO_DO' | 'DOING' | 'DONE'
export enum Categories {
'TO_DO' = 'TO_DO',
'DOING' = 'DOING',
'DONE' = 'DONE',
}
export interface IToDo {
text: string | undefined
id: number
category: Categories
}
// 새 toDo를 추가하면, toDoState로 들어감
export const toDoState = atom<IToDo[]>({
key: 'toDo',
default: [],
})
// category atom은 ToDoList.tsx의 onInput함수에서 수정되고 있음
export const categoryState = atom<Categories>({
key: 'category',
default: Categories.TO_DO,
})
// selector는 key와 get함수(selector가 어떤 것을 반환할지 결정)를 가진다.
export const toDoSelector = selector({
key: 'toDoSelector',
get: ({ get }) => {
// toDoState가 변할 때마다 selector 실행
const toDos = get(toDoState)
// categoryState가 변할 때마다 selector 실행
const category = get(categoryState)
// category에 따라서 selector가 각각의 toDo 배열을 반환
return toDos.filter(toDo => toDo.category === category)
},
})
▶ CreateToDo.tsx
- 새로운 할 일을 생성하는 컴포넌트
- useRecoilValue: selector에서 반환된 값을 가져온다.
- useRecoilState: categoryState의 값을 가져오고, 해당 값을 변경하는 setCategory 함수도 가져온다.
- onInput함수: select 엘리먼트의 값이 변할 때 호출되는 콜백 함수이다. 선택된 값을 setCategory를 사용하여 업데이트한다.
// CreateToDo.tsx
import { useForm } from "react-hook-form";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { categoryState, toDoState } from "../atoms";
interface IForm {
toDo: string;
}
function CreateToDo() {
const setToDos = useSetRecoilState(toDoState);
const category = useRecoilValue(categoryState);
const { register, handleSubmit, setValue } = useForm<IForm>();
const handleValid = ({ toDo }: IForm) => {
setToDos((oldToDos) => [
{ text: toDo, id: Date.now(), category },
...oldToDos,
]);
setValue("toDo", "");
};
return (
<form onSubmit={handleSubmit(handleValid)}>
<input
{...register("toDo", {
required: "Please write a To Do",
})}
placeholder="Write a to do"
/>
<button>Add</button>
</form>
);
}
export default CreateToDo;
▶ ToDoList.tsx
- 할 일 목록을 렌더링 하고, 카테고리 선택과 관련된 상태 및 이벤트 처리를 담당하는 컴포넌트
- useRecoilValue(toDoSelector): 현재 선택된 카테고리에 따라 필터링된 할 일 목록을 가져온다.
- useRecoilState(categoryState): 현재 선택된 카테고리와 해당 카테고리를 변결하는 setCategory 함수를 가져온다.
- onInput 함수: 카테고리 선택을 처리한다.
- 카테고리 선택을 위한 select 엘리먼트를 렌더링하고, CreateToDo컴포넌트와 할 일 목록을 렌더링 한다.
// ToDoList.tsx
import { useRecoilState, useRecoilValue } from 'recoil'
import { Categories, categoryState, toDoSelector, toDoState } from './atoms'
import CreateToDo from './CreateToDo'
import ToDo from './ToDo'
const ToDoList = () => {
// selector에서 반환한 값을 받아서 각각 render
// state를 변형하는 것이 아니라 output을 변형하는 것이다. 이것이 selector의 요점
const toDos = useRecoilValue(toDoSelector)
// useRecoilState은 값과 modifier함수 제공
const [category, setCategory] = useRecoilState(categoryState)
// option의 value값을 가져옴
// onInput함수는 select의 value를 가져다가 setCategory 함수에 넣어주고 있음
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value as any)
}
console.log(category)
return (
<div>
<h1>To Dos</h1>
<hr />
<select value={category} onInput={onInput}>
<option value={Categories.TO_DO}>To Do</option>
<option value={Categories.DOING}>DOING</option>
<option value={Categories.DONE}>DONE</option>
</select>
<CreateToDo />
{toDos?.map(todo => (
<ToDo key={todo.id} {...todo} />
))}
</div>
)
}
export default ToDoList
▶ ToDo.tsx
- 각 할 일 항목을 렌더링 하고, 카테고리 변경 시 상태를 업데이트하는 컴포넌트
- useSetRecoilState: toDoState의 값을 업데이트할 수 있는 setToDos 함수를 가져온다.
// ToDo.tsx
import { useSetRecoilState } from 'recoil'
import { Categories, IToDo, toDoState } from './atoms'
const ToDo = ({ text, category, id }: IToDo) => {
const setToDos = useSetRecoilState(toDoState)
// 버튼 클릭 시 호출되는 onClick함수
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name },
} = event
// 현재 target의 경로 찾기
setToDos(oldToDos => {
// 카테고리를 바꾼 todo의 인덱스 찾기
const targetIndex = oldToDos.findIndex(toDo => toDo.id === id)
// 새로 입력한 todo를 생성
const newToDo = { text, id, category: name as any }
// 카테고리를 바꾼 todo를 원래 자리에 두기 위해 배열을 업데이트
return [
...oldToDos.slice(0, targetIndex),
newToDo,
...oldToDos.slice(targetIndex + 1),
]
})
}
return (
<li>
<span>{text}</span>
{category !== Categories.DOING && (
<button name={Categories.DOING} onClick={onClick}>
Doing
</button>
)}
{category !== Categories.TO_DO && (
<button name={Categories.TO_DO} onClick={onClick}>
To Do
</button>
)}
{category !== Categories.DONE && (
<button name={Categories.DONE} onClick={onClick}>
Done
</button>
)}
</li>
)
}
export default ToDo
// index.tsx
import React 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>
<App />
</RecoilRoot>
</React.StrictMode>
)
// App.tsx
import ToDoList from './components/ToDoList'
function App() {
return (
<div>
<h1>My To-Do List App</h1>
<ToDoList />
</div>
)
}
export default App
'React' 카테고리의 다른 글
[React] Drag and Drop (0) | 2023.08.12 |
---|---|
[React] atom, selector를 사용한 minutes, hours 변환기 (0) | 2023.08.11 |
[React] react hook form (0) | 2023.08.10 |
React Router V6 (0) | 2023.08.02 |
TypeScript props, state, forms, themes (0) | 2023.08.01 |