1.7 React masterClass (ToDoList1)

hun2__2·2022년 1월 7일
0

Have a fruitful vacation

목록 보기
11/24

이번에는 recoil을 연습하기 위해서 ToDoList를 만들어본다.

그전에 앞서서 form을 간편하게 만들게 도와주는 react-hook-form부터 배워보았다.

먼저 기존의 방식으로 form을 만들때는

import { useState } from "react";

function ToDoList() {
  const [toDo, setToDo] = useState("");
  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = event;
    setToDo(value);
  };
  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(toDo);
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input onChange={onChange} value={toDo} placeholder="ToDoList" />
        <button>add</button>
      </form>
    </div>
  );
}

export default ToDoList;

이렇게 만들어줬었다. 만약 여기서 input이 더 늘어난다면 그것에 맞게 useState값과 onChange function을 만들어 줘야한다... form만 만드는데 100줄이상 필요해질 수도 있어진다.
또한 validation을 하기위해서 submit에 조건들을 모두 만들어 줘야 했다.

하지만 react-hook-form을 사용하면 매우 간편하게 바뀔 수 있다! 심지어 form validation도 도와준다!

언제나 그렇듯 먼저 설치해준다.

npm install react-hook-form

설치가 끝나면 useForm()을 사용해서 form을 만들어줄 수 있다.
useForm()안에는 아래와같은 기능들이 있다.

regitser는 사용자의 입력받는 input으로 각각의 필드를 만들어주는 것인데 각각의 state를 만들어주는 것이다.
watch()로 값을 확인해 줄 수 있고
setValuse로 값을 넣어줄 수 있다.
handleSubmit 로 submit의 역할을 대신할 수 있다.
regitser에 조건을 추가하는 방법으로 validation를 작성할 수 있다.
그리고 formState안의 errors를 사용해서 어느 항목에 error가 생긴건지도 확인 할 수 있다.
내장되어있는 조건이 아닌 내가 조건을 만들어 검사하고싶을때는 setError를 사용할 수 있다.
예를 들어보면

interface IForm {
  Email: string;
  PassWord1: string;
  PassWord2: string;
  FirstName: string;
  LastName: string;
  UserName: string;
  extraError?: string;
}

function ToDoList() {
  const {
    register,
    handleSubmit,
    formState: { errors },
    setError,
    setValuse
  } = useForm<IForm>({
    defaultValues: {
      Email: "@naver.com",
    },
  });
  const onValid = (data: IForm) => {
    if (data.PassWord1 !== data.PassWord2) {
      setError(
        "PassWord1",
        { message: "비밀번호가 다릅니다." },
        { shouldFocus: true }
      );
    }
    // ! 필요한 검사를 하고  error를 만들 수 있다.
    // if (Object.keys(errors).length === 0) {
    //   setError("extraError", {
    //     message: "외부요인으로 인해 로그인 할 수 없습니다.",
    //   });
    // }
    setValuse=()
  };
  console.log("errors", errors);
  return (
    <div>
      <Form onSubmit={handleSubmit(onValid)}>
        <input
          {...register("Email", {
            required: "이메일이 필요합니다.",
            pattern: {
              value: /^[A-Za-z0-9._%+-]+@naver.com$/,
              message: "이메일은 @naver.com로 끝나야 합니다",
            },
          })}
        />
        <span>{errors?.Email?.message}</span>
        <input
          {...register("PassWord1", {
            required: "비밀번호가 필요합니다.",
            minLength: {
              value: 4,
              message: "비밀번호가 너무 짧습니다.",
            },
            validate: (value) =>
              value.includes("jaehun@naver.com")
                ? "jaehun은 내꺼에요 다른거 쓰세요"
                : true,
          })}
          placeholder="PassWord1"
        />
        <span>{errors?.PassWord1?.message}</span>
        <input
          {...register("PassWord2", {
            required: "비밀번호가 필요합니다.",
            minLength: {
              value: 4,
              message: "비밀번호가 너무 짧습니다.",
            },
          })}
          placeholder="PassWord2"
        />
        <span>{errors?.PassWord2?.message}</span>
        <input
          {...register("FirstName", {
            required: "성이 필요합니다.",
          })}
          placeholder=""
        />
        <span>{errors?.FirstName?.message}</span>
        <input
          {...register("LastName", { required: "이름이 필요합니다." })}
          placeholder="이름"
        />
        <span>{errors?.LastName?.message}</span>
        <input
          {...register("UserName", {
            required: "닉네임이 필요합니다.",
            minLength: {
              value: 4,
              message: "닉네임이 너무 짧습니다.",
            },
            maxLength: {
              value: 10,
              message: "닉네임이 너무 깁니다.",
            },
            validate: {
              noHun: (value) =>
                value.includes("hun") ? "hun은 내꺼에요 다른거 쓰세요" : true,
              noJaeHun: (value) =>
                value.includes("jaehun")
                  ? "jaehun은 내꺼에요 다른거 쓰세요"
                  : true,
            },
          })}
          placeholder="닉네임"
        />
        <span>{errors?.UserName?.message}</span>
        <button>add</button>
        <span>{errors?.extraError?.message}</span>
      </Form>
    </div>
  );
}

와 같이 작성한다면

각 input값을 watch()를 통해서 볼수있다


{...register()}인수안에 고유값과 validation을 체크하기 위한 조건들을 담은 obj를 넣을 수 있는데 여기에 required를 넣는게 좋은점이 HTML으로 requeired를 만든다면 사용자가 접근할 수 있어 변경이 가능하지만 JS로 넣어놓으면 접근할 수 없다.
requeired외에도 minLength, maxLength, pattern 등 여러가지 조건들을 키값으로 만들어 obj에 넣을 수 있고 해당값에는 그냥 값을 넣던가 값과 message를 포함반 객체를 넣는 방법이 있다. 또한 validatae를 이용해서도 조건을 만들 수 있다.
한개일 때는

{...register("PassWord1", {
            required: "비밀번호가 필요합니다.",
            minLength: {
              value: 4,
              message: "비밀번호가 너무 짧습니다.",
            },
            validate: (value) =>
              value.includes("jaehun@naver.com")
                ? "jaehun은 내꺼에요 다른거 쓰세요"
                : true,
          })}

이렇게 넣을 수 있고
두개 이상 일 때 함수로 만들어서 검사할 수 있다.

validate: {
              noHun: (value) =>
                value.includes("hun") ? "hun은 내꺼에요 다른거 쓰세요" : true,
              noJaeHun: (value) =>
                value.includes("jaehun")
                  ? "jaehun은 내꺼에요 다른거 쓰세요"
                  : true,
            },

(register 타입)


validation을 만족하지 않는다면 그 타입과 내가 적어둔 message를 확인할 수 있다.

error가 생기는 조건을 만들 때는 2가지 종류가 있다 .


또한 setError를 이용해서 제공하는 error말고 내가 원하는 조건에 맞게 error를 만들 수도 있다
이렇게 조건을 만들고 handleSubmit(onValid)를 해주면 register()내의 조건들을 먼저 체크 한 후 submit할때 내가 만든 error를 체크해준다.

(참고 : https://react-hook-form.com/get-started#Quickstart)

또 새로운 좋은 hook을 알아갔다...!! 나도 빨리 잘해져서 이런 거 내가 찾아서 사용해보고 알려주고, 내가 직접 만들고 싶다ㅎㅎ,,

이제 예전방식은 내던지고 react-hook-form을 이용해서 만들어보면


function ToDoList() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();
  const valid = (data: any) => {
    console.log(data);
  };
  return (
    <Container>
      <h1>TodoList</h1>
      <form onSubmit={handleSubmit(valid)}>
        <input
          {...register("toDo", { required: "TodoList를 작성해주세요" })}
          placeholder="ToDoList"
        />
        <button>add</button>
        <span>{errors?.toDo?.massage}</span>
      </form>
      <ul></ul>
    </Container>
  );
}

export default ToDoList;

이렇게 작성 할 수 있다.

여기서 data의 타입이 any가 아니라 뭐가 되야할까?

data로 받아오는 값을 보면
이런값을 받아온다
input으로 받는 data는 필드 이름을 key값으로 갖고 타입은 string이 된다.

interface IDataProps {
  toDo: string;
}

register와 data의 타입은 IDataProps가 된다.
이제 register로 만든 toDo 필드에는 어떤 값들이 필요할지 고민해보자

먼저 input으로 받은 text와 여러개의 toDo값들을 받을것이므로 id가 필요하고 어떤 상태인지 알기 위한 카테고리 값이 필요할것같다. 그리고 recoil의 atom을 이용해서 ToDo값들을 관리해야지 다른 컴포넌트에서도 사용하기 편리할 것 같다.

먼저 ToDo값을 받을 수 있는 atom으로 state를 만들어 주고 그의 타입을 지정해주자

import { atom } from "recoil";

export interface IToDoProps {
  text: string;
  id: number;
  category: "TO_DO" | "DONE" | "DOING";
}

export const ToDoState = atom<IToDoProps[]>({
  key: "todolist",
  default: [],
});

이제 useRecoilState를 이용해서 atom의 dafault값에 input으로 받은 값과 id, category를 넣어준다.

const [toDoList, setToDoList] = useRecoilState(ToDoState);

  const valid = (data: IDataProps) => {
    console.log("data", data);
    setToDoList((oldtodo) => [
      { text: data.toDo, id: Date.now(), category: "TO_DO" },
      ...oldtodo,
    ]);
    setValue("toDo", "");
  };


잘 들어온다!

이제 입력받은 todo들로 이루어진 list를 보여주면

      <ul>
        {toDoList?.map((todo, idx) => (
          <li>
          	{todo.text}
            {todo.category}
		  </li>
        ))}
      </ul>

잘 나오는 것을 확인할 수 있다!

이제 이 카테고리를 이용해서 todoList의 상태를 나태날 수있게 해줄 것이다

그 전에 ToDoList.tsx를 기능별로 나누어 입력받는 form과 보여주는 List를 컴포넌트 화 시켜주고 카테고리에 따라 보여주는것을 달리해줄 것이다.

내일 할거다.. 넘 피곤하다...

ps.
오늘도 늦잠을 잤다ㅜ 아침에 영어 데일리 미션 못했다...
내일은 일찍 일어나서 데일리 미션 하고 과외가야겠다!
그래도 운동 다시 시작해서 좋다!
몸은 쓰레기가 된 것 같다,,, 다시 열심히 해봐야지!

profile
과정을 적는 곳

0개의 댓글