so woon!

[RECOIL] 카테고리별로 todolist 작성하기 + enum으로 카테고리 관리하기 본문

Recoil/개념정리

[RECOIL] 카테고리별로 todolist 작성하기 + enum으로 카테고리 관리하기

xowoony 2023. 4. 21. 17:09

학습일 : 2023. 04. 21


 

저번 글까지는 새 toDo를 추가하면
가장 먼저 TODO 카테고리로 들어갔었다.

하지만 이렇게 말고
처음 TODO를 생성할때 카테고리를 선택해서 적어줄 수 있게 해보자.
toDo의 카테고리가 categoryState에 따라서 추가되게 해보도록 하자.


atoms.tsx 에 적어주었던 categoryState에 따라서
새 toDo의 카테고리가 정해졌으면 좋겠다.


<CreateToDo.tsx>
카테고리를 얻어오도록 한다. 값만 얻고 싶기 때문에 useRecoilValue를 사용하고
categoryState atom을 불러온다.
  const category = useRecoilValue(categoryState); 작성
그리고 setToDos에서 category: category 를 작성한다. (단축 문법으로 category만 작성해도 되어서 그렇게 하겠음)

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) => [
      ...oldToDos,
      { text: toDo, category: "TODO", id: Date.now(), category },
    ]);
    setValue("toDo", "");
  };
  return (
    <form onSubmit={handleSubmit(handleValid)}>
      <input
        {...register("toDo", {
          required: "please write a to do",
        })}
        placeholder="오늘 해야할 일을 입력하세요"
      />
      <button>add</button>
    </form>
  );
}

export default CreateToDo;





category는 그냥 string인데, toDo의 category는 세 종류로 제한되어서 에러가 생기기 때문에
타입스크립트에게 categoryState가 세개 중 하나일 거라고 알려주면 되므로


<atoms.tsx>
categoryState에 이렇게 작성해주면 타입스크립트에게
세개 중 하나가 될 것이라고 알려주게 된다.

// categoryState
export const categoryState = atom<"TODO" | "DOING" | "DONE">({
  key: "category",
  default: "TODO",
});

 

 


<ToDoList.tsx>
as any를 붙여넣어 주고

  const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
    setCategory(event.currentTarget.value as any); // 일단은 any로 두기로 하고 나중에 고쳐보도록 하자.
  };



좀 번거롭기 때문에
카테고리 type을 만들어주겠다.

 

 


<atom.tsx>
type을 만들어주고
atom<"TODO" | "DOING" | "DONE">을
atom<categories>로 수정한다.

import { atom, selector } from "recoil";

export interface IToDo {
  text: string;
  id: number;
  category: "TODO" | "DOING" | "DONE";
}
 // type
 type categories = "TODO" | "DOING" | "DONE";


// categoryState
export const categoryState = atom<categories>({
  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);
    return toDos.filter((toDo) => toDo.category === category);
  },
});




일단 실행시켜보면
이제 그 카테고리에 들어와서
뭔가를 적어주면
그 카테고리에 바로 추가할 수 있게 되었다.


 

 


이제 toDo는 카테고리에 기반해서 추가되고 있다.
그리고 prop들을 이리저리 보내지 않아도 된다
필요할 때 atom을 가져오고, 또 필요할 때 그것을 수정할 수 있게 된 것이다.

 



다음으로 수정해볼 부분이 있는데,

 

 


ToDoList.tsx 에서 보면 option에 value로 TODO, DOING, DONE 그리고 
다른 어디선가에서는 category
이렇게 서로 다르게 써주고 있는데
type보다 조금 더 멋진 것을 사용해보도록 하겠다
type은 그냥 복붙을 안하게 해주는 단순한 문법이었다.
내가 원하는 것은 코드 전체에서 이것을 각각 사용할 수 있게 만드는 것이다.
실수를 방지하기 위해서 말이다.

 

 


그러니 이것을 type 대신 enum을 만들어서 해보도록 하자.

 

 


<atoms.tsx>
enum 을 작성해준다.
이제 "TODO"나 "DOING", "DONE"을 손으로 쓸 일은 없게 될 것이다.
이제 IToDo의 category는 Categories enum 중 하나라는 것을 알려주도록 하자
category: Categories; 를 작성
그리고 categoryState에도 바꿔주고 default를 Categories.TODO로 바꿔주도록 한다.

import { atom, selector } from "recoil";

// enum
export enum Categories {
  "TODO",
  "DOING",
  "DONE"
}

// interface
export interface IToDo {
  text: string;
  id: number;
  category: Categories;
}

// categoryState
export const categoryState = atom<Categories>({
  key: "category",
  default: Categories.TODO,
});

// toDoState
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);
    return toDos.filter((toDo) => toDo.category === category);
  },
});



원하는 string들로 enum을 만들고
enum이름.뭐뭐
이렇게 사용하면 된다.


enum을 만들어 줬으니 이제
사용하러 가보도록 하겠다.

 

 


<ToDoList.tsx>
원래의 코드를

<select value={category} onInput={onInput}>
    <option value="TODO">TODO</option>
    <option value="DOING">DOING</option>
    <option value="DONE">DONE</option>
</select>



이렇게 바꾸었다.

   

<select value={category} onInput={onInput}>
	<option value={Categories.TODO}>TODO</option>
    <option value={Categories.DOING}>DOING</option>
    <option value={Categories.DONE}>DONE</option>
</select>




이제 ToDo 컴포넌트로 가보도록 하자
이곳은 문제가 많아졌다.

 

 


<ToDo.tsx>
여기서 category는 enum Categories 타입인데, 그걸 string 타입과 비교하고 있다.
enum으로 바꿔주도록 하겠다. name도 바꿔주겠음.

수정 전

return (
    <li>
      <span>{text}</span>
      {category !== "TODO" && (
        <button name="TODO" onClick={onClick}>
          TODO
        </button>
      )}
      {category !== "DOING" && (
        <button name="DOING" onClick={onClick}>
          DOING
        </button>
      )}
      {category !== "DONE" && (
        <button name="DONE" onClick={onClick}>
          DONE
        </button>
      )}
    </li>
  );



수정 후

  return (
    <li>
      <span>{text}</span>
      {category !== Categories.TODO && (
        <button name={Categories.TODO} onClick={onClick}>
          TODO
        </button>
      )}
      {category !== Categories.DOING && (
        <button name={Categories.DOING} onClick={onClick}>
          DOING
        </button>
      )}
      {category !== Categories.DONE && (
        <button name={Categories.DONE} onClick={onClick}>
          DONE
        </button>
      )}
    </li>
  );




수정 후 name 부분이 오류가 생기는데
Categories enum 을 살펴보면
hover시
TODO는 0,
DOING은 1,
DONE은 2 이다.

Categories.DOING이 아닌 category를 선택하려고 할 때
사실은 category가 1이 아니라는 것을 확인하고 있는 것이다.
왜냐하면 기본적으로 enum은 일련의 숫자를 문자로 표현해주기 때문이다.

여기서 문제는 버튼의 name이 숫자여서는 안된다는 것이다.
string이어야 한다.

그러니 지금 당장은 string으로 바꿔주도록 하자.


<ToDo.tsx>

 return (
    <li>
      <span>{text}</span>
      {category !== Categories.TODO && (
        <button name={Categories.TODO + ""} onClick={onClick}>
          TODO
        </button>
      )}
      {category !== Categories.DOING && (
        <button name={Categories.DOING + ""} onClick={onClick}>
          DOING
        </button>
      )}
      {category !== Categories.DONE && (
        <button name={Categories.DONE + ""} onClick={onClick}>
          DONE
        </button>
      )}
    </li>
  );



그리고 toDo가 어떤 모습인지 콘솔에 찍어보면

 

 


<ToDoList.tsx>

function ToDoList() {
    ...
    console.log(toDos);
    ...
}




 

 


카테고리는 0이고
모두가 같은 enum을 참조하기 때문에 문제없이 작동하고 있다.
enum은 기본적으로 number이긴 하지만

 

 


<atoms.tsx>

// enum
export enum Categories {
  "TODO" = "TODO",
  "DOING" = "DOING",
  "DONE" = "DONE"
}



이런식으로 string으로 바꾸어줄 수 있다.

 

 


string으로 바꾸어주었기 때문에 아까 적어주었던
+ "" 는 지워주도록 하겠다.

 

 


<ToDo.tsx>

 return (
    <li>
      <span>{text}</span>
      {category !== Categories.TODO && (
        <button name={Categories.TODO} onClick={onClick}>
          TODO
        </button>
      )}
      {category !== Categories.DOING && (
        <button name={Categories.DOING} onClick={onClick}>
          DOING
        </button>
      )}
      {category !== Categories.DONE && (
        <button name={Categories.DONE} onClick={onClick}>
          DONE
        </button>
      )}
    </li>
  );




실행결과

 

 

 

Comments