한 걸음씩

[React] To Do 본문

React

[React] To Do

winter17 2023. 8. 11. 16:48

 

 

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