[원티드 프리온보딩] 프론트엔드 챌린지 사전 과제 - todo list

바질·2023년 1월 10일
0

프리온보딩 프론트엔드 챌린지 1월
CRUD w React Query

원티드 프리온보딩 프론트엔드 챌린지를 시작하며,
지난 글 보러가기
프리온보딩 프론트엔드 챌린지 사이트
프리온보딩 프론트엔드 챌린지 사전과제

사전 과제는 필수가 아니지만 효과적인 기술 역량 향상을 위해 사전 미션을 수행 후 참여하기를 권장하고 있다.

원티드 프리온보딩 프론트엔드에서 강의하는 언어와 프레임워크는 Javascript(Typescript)와 React이다.

함수 컴포넌트로 작성해야 한다.(React Hooks)

자세한 것은 사이트를 참고.

참여한 이유는 역량 향상에 있다. 개발 공부를 혼자 하면서 제일 취약했던 점은 타인의 코드를 보거나 의견을 나눌 수 없어 이렇게 사용하는 게 맞는지에 대한 확신이 없었다. 이번 챌린지는 짧게 진행되지만 해당 챌린지에 참여하면서 많은 공부가 될 것 같아 신청했다.

이번에 다루는 주제는 비동기이다. 따라서, React Query에 대해 공부하고 다음에 또 다른 주제로 챌린지가 나오면 다시 참여 신청을 하고 싶다.

아래는 사전 과제를 수행한 코드와 과정을 기록해놓았다.

요구하는 기능은 깃헙 주소로 들어가면 auth, todo 별로 확인할 수 있다.

간단한 회원가입/로그인과 todo를 구현하는 과제이다.
기능을 구현하는 데 있어서 이틀을 썼고, 하루는 회원가입/로그인을 구현하고 하루는 todo를 구현했다.


아래부터는 to do list를 구현하는 과제에 대한 기록이다.

목차

코드 리팩토링을 하여 크게 3가지의 GetTodos, CreateTodoList, ChangeTodos 컴포넌트로 나누었다.

CreateTodoList

먼저, CreateTodoList부터 보자. 마크업은 form,input으로 이루어져있다. 각각, onSubmit, onChange 함수를 걸어두었다.

CreateTodoList.tsx

 const [createTodo, setCreateTodo] = useState<ITodo>({
    title: "",
    content: "",
  });

const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { name, value },
    } = event;
    setCreateTodo({ ...createTodo, [name]: value });
  };

input 값이 변경되면 해당 값을 createTodo에 담는다. 그리고나서 form을 제출할 때 onSubmit 함수를 호출하는데,

 const setChangeTodo = useSetRecoilState(isChange);
 const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const token = localStorage.getItem("token");
   // api 함수 호출
    const data = await createTodoApi(createTodo, token || "");
    if (data) {
      console.log("성공", data);
      setChangeTodo((prev) => !prev);
      event.currentTarget.reset();
    }
  };

api.ts


export const createTodoApi = async (
  { title, content }: ITodo,
  token: string
) => {
  const response = await fetch(`${process.env.REACT_APP_API_URL}/todos`, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
      Authorization: token,
    },
    body: JSON.stringify({
      title,
      content,
    }),

data를 post 할 때, token과 body가 필요하니 인수로 보내준다. 저번과 같이 응답이 성공하면, 성공한 데이터가 반환값으로 돌아오니 if 조건문을 활용했다.

setChangeTodotrue/false의 값으로 전역 상태 관리를 해준다. 이건 나중에 GetTodos에서 나오니 뒤로 밀어두고, state를 변경해준 뒤, input의 값을 없애준다.

GetTodos

GetTodos 마크업은 ul, li로 이루어져있다. useQuery 를 이용해 데이터를 불러오고 처리한다. 불러온 데이터를 map 메서드로 나열한다.

GetTodos.tsx

  const setId = useSetRecoilState<string | undefined>(todoId);
  const isChangeValue = useRecoilValue(isChange);
  const token = localStorage.getItem("token");
  const { isLoading: todosIsLoading, data: todos } = useQuery<ITodos[]>(
    ["todos", isChangeValue],
    () => getTodosApi(token || "")
  );

api.ts


export const getTodosApi = async (token: string) => {
  const response = await fetch(`${process.env.REACT_APP_API_URL}/todos`, {
    headers: {
      Authorization: token,
    },
  });
  const { data } = await response.json();

  return data;
};

필요한 인수를 api 함수에 보내주고, 그걸 useQuery가 관리해준다. 즉, 데이터를 불러오고 데이터 값의 변경이 없다면 불러오지 않고 기존의 데이터를 사용하는 것. 그러나, 나는 query key에 전역 변수로 isChangeValue를 넣어주었다.

query key의 값이 변동되면 useQuery는 다시 데이터를 요청한다. 아까 생성한 todo의 값을 실시간으로 보여주기 위해서 query key에 변수를 넣었다. 이러면 todo가 생성될 때마다 데이터를 새롭게 요청해오고, 나중에 다시 나오겠지만 todo의 값이 수정(삭제/변경)될 때에도 새로 요청하게 된다.

todo의 id를 전역 변수에 넣은 것은 Home.tsx에서 조건 렌더링을 위함이다. 클릭했을 때 전역 변수에 id를 넣고, id가 있다면 해당 todo의 값을 보여주는 식이다.

ChangeTodos

사전 과제에서 요구하는 기능은 to do list를 보여주는 것과 to do를 생성하는 것도 있지만 to do를 변경(삭제/수정)하면 이것을 실시간으로 반영해주어야 한다는 것이다.

따라서 수정할 수 있는 input과 삭제할 수 있는 button이 필요하다.
input에 값이 들어오면 해당 값으로 변경해주고, 아니라면 기존의 값을 유지해야한다. 구현한 로직은 생성했을 때와 동일하다.

ChangeTodos.tsx

const [isDisabled, setIsDisabled] = useState(false);
  const params = useParams();
  const setChangeTodo = useSetRecoilState(isChange);
  const [todo, setTodo] = useState<ITodo>({
    title: "",
    content: "",
  });

  useEffect(() => {
    setIsDisabled(false);
  }, [params.id]);
  const id = useRecoilValue(todoId);
  const token = localStorage.getItem("token");
  const queryClient = useQueryClient();
  const { isLoading, data } = useQuery<ITodos>(["todo", id], () =>
    getTodoByIdApi(token || "", id || "")
  );

  const updateTodoFn = useMutation(
    (newTodo: INewTodo) => updateTodoApi(newTodo),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["todo", id]);
        setChangeTodo((prev) => !prev);
      },
      onError: () => {
        console.log("실패");
      },
    }
  );

  const deleteTodoFn = useMutation((config: IDelete) => deleteTodoApi(config), {
    onSuccess: () => {
      console.log("성공");
      queryClient.invalidateQueries(["todo", id]);
      setChangeTodo((prev) => !prev);
    },
  });

  const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    if (!isDisabled) return;
    event.preventDefault();
    console.log(todo);

    const newTodo = {
      ...todo,
      id: id || "",
      token: token || "",
    };
    updateTodoFn.mutate(newTodo);
  };

우선, useQuery를 쓰면서 데이터의 값을 실시간으로 변경하는 것에 있어 어려움을 겪었다. todo를 생성하거나 삭제, 수정하면 이것을 실시간으로 변경해주지 않고 새로고침을 통해서만 변경되었다.

따라서 이것을 실시간으로 반영해주기 위해 구글링 해보니 위에서 설명한 것처럼 query key를 변경해주는 것이었다. 시도한 결과 실시간 반영은 성공했지만 딜레이가 있는지 화면이 깜박거리는 버그가 있었다.

이를 위해 useQuery가 아닌 useMutation을 사용해보기로 했다.
(어쨌든, 상세 내용을 보여주기 위해서 useQuery도 사용하여 데이터를 불러와야 한다.)

useMutation은 데이터를 변경하거나 삭제할 때 사용된다고 한다. 자세한 사용 방법은 구글링 해보기를 추천한다.

useMutation을 사용하기 위해서, 즉, 데이터의 값을 실시간으로 반영해주기 위해서는 query key의 유효성을 없애주어야 한다.

const queryClient = useQueryClient();

 const updateTodoFn = useMutation(
    (newTodo: INewTodo) => updateTodoApi(newTodo),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["todo", id]);
        setChangeTodo((prev) => !prev);
      },
      onError: () => {
        console.log("실패");
      },
    }
  );
  
  const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    if (!isDisabled) return;
    event.preventDefault();
    const newTodo = {
      ...todo,
      id: id || "",
      token: token || "",
    };
    updateTodoFn.mutate(newTodo);
  };

위의 코드로 queryClient를 불러오고, useQuery를 사용했을 때 query key로 넣어준 값을 동일하게 넣어준다. 이렇게 하면 query key의 유효성이 없어져 다시 데이터를 불러오게 되는 것이다. 이를 함수로 만들고 onSubmit 함수를 호출 할 때마다 updateTodoFn 함수를 호출하게 했다.

삭제 버튼도 이것과 같다. 단, Home.tsx에서 불러오는 GetTodos 컴포넌트의 api는 useQuery로 사용했기에 위에서 설명한 전역 변수의 값을 변경하여 key값을 변경해주어야 한다.

위의 코드로 작성하니 원하는 것처럼 데이터가 깜박거림 없이 실시간 반영되었다. 만족!

(useQuery는 query key로 데이터를 구별하기 때문에 다른 api를 함수로 불러왔어도 key가 같으면 동일한 데이터를 불러온다. 그러니 api가 다르고 불러올 데이터가 다르다면 key의 값도 다르게 해주어야 한다.)


이상으로 비동기를 이용한 to do list 구현을 마치겠다.

작성한 사전과제를 이용하여 강의를 들으며 리팩토링하는 것이 이번 챌린지의 내용이다. 궁금하다면 사이트를 방문하여 확인해보는 것도 좋을 것 같다.

듣기로는 꽤 괜찮다고 하여 기대하고 있다. 다음달에도 챌린지를 기획 중이라고 하니 신청을 못했다면 2월을 노려보는 것도...

0개의 댓글