[React] useState + useEffect 대신 useMutation 사용하기: Create, Detail read, Update, Delete

KoEunseo·2023년 1월 16일
0

리액트

목록 보기
10/21

useQuery는 get할때 사용하는 메서드였다.
create했을때 리스트가 업데이트되지않아서 당황했음.
refresh를 삭제해서 그런거겠지만... 리액트쿼리에 이부분을 위한 메서드가 있을거라고 생각했다.
찾아보니 useMutation이라는 것을 사용해서 CUD를 하고, 여기서 refetch해주는 게 따로 있다고 함.

리액트 쿼리 mutation
https://react-query-v3.tanstack.com/guides/mutations

Create

Before

import Input from "../../../common/Input/Input";
import Button from "../../../common/button/Button";
import { useState } from "react";
import { createTodo } from "../../../api/TodoApi";
import { AiFillCloseSquare } from "react-icons/ai";
import * as TodoCreateStyle from "./TodoCreateStyle";
import Textarea from "../../../common/textaea/Textarea";
import IconButton from "../../../common/iconButton/IconButton";

const TodoCreate = ({ refresher, handleAddMode }) => {
  const [todoContent, setTodoContent] = useState({
    title: "",
    content: "",
  });
  const { title, content } = todoContent;
  const handleTodoContent = (e) => {
    const { value, name } = e.target;
    setTodoContent({
      ...todoContent,
      [name]: value,
    });
  };
  const reset = (e) => {
    setTodoContent({
      title: "",
      content: "",
    });
  };
  function handleCreateTodo(e) {
    e.preventDefault();
    const payload = todoContent;
    createTodo("/todos", payload);
    reset(e);
    refresher();
  }
  return (
    <>
      <TodoCreateStyle.CreateForm onSubmit={handleCreateTodo}>
        <TodoCreateStyle.CreateTitle>
          <h3>Todo 추가하기</h3>
          <IconButton onClick={handleAddMode} type="submit">
            <AiFillCloseSquare />
          </IconButton>
        </TodoCreateStyle.CreateTitle>
        <Input
          name="title"
          id="todo Title"
          type="text"
          placeholder="오늘 할 일을 적어보세요."
          border="border"
          value={title}
          onChange={handleTodoContent}
        />
        <Textarea name="content" value={content} onChange={handleTodoContent} />
        <Button styles="default" type="submit">
          Add Todo
        </Button>
      </TodoCreateStyle.CreateForm>
    </>
  );
};

export default TodoCreate;

After

import Input from "../../../common/Input/Input";
import Button from "../../../common/button/Button";
import { useState } from "react";
import { createTodo, updateTodo } from "../../../api/TodoApi";
import { AiFillCloseSquare } from "react-icons/ai";
import * as TodoCreateStyle from "./TodoCreateStyle";
import Textarea from "../../../common/textaea/Textarea";
import IconButton from "../../../common/iconButton/IconButton";
import { useMutation, useQueryClient } from "react-query";

const TodoCreate = ({ refresher, handleAddMode }) => {
  const [todoContent, setTodoContent] = useState({
    title: "",
    content: "",
  });
  const { title, content } = todoContent;
  const handleTodoContent = (e) => {
    const { value, name } = e.target;
    setTodoContent({
      ...todoContent,
      [name]: value,
    });
  };
  const reset = (e) => {
    setTodoContent({
      title: "",
      content: "",
    });
  };
  const queryClient = useQueryClient();
  const { mutate, isLoading } = useMutation(createTodo, {
    //Updating the cached data using the response data
    onSuccess: (newTodo) => {
      queryClient.invalidateQueries(["todo"], newTodo);
    },
  });
  function handleCreateTodo(e) {
    e.preventDefault();
    mutate(todoContent);
    reset(e);
  }
  if (isLoading) {
    return <div>is Loading...</div>;
  }
  return (
    <>
      <TodoCreateStyle.CreateForm onSubmit={handleCreateTodo}>
        <TodoCreateStyle.CreateTitle>
          <h3>Todo 추가하기</h3>
          <IconButton onClick={handleAddMode} type="submit">
            <AiFillCloseSquare />
          </IconButton>
        </TodoCreateStyle.CreateTitle>
        <Input
          name="title"
          id="todo Title"
          type="text"
          placeholder="오늘 할 일을 적어보세요."
          border="border"
          value={title}
          onChange={handleTodoContent}
        />
        <Textarea name="content" value={content} onChange={handleTodoContent} />
        <Button styles="default" type="submit">
          Add Todo
        </Button>
      </TodoCreateStyle.CreateForm>
    </>
  );
};
export default TodoCreate;
queryClient.setQueryData('todos', old => [...old, newTodo])

이런식으로 작성했더니 바로 업데이트가 안된다.
위에서처럼

queryClient.invalidateQueries(["todo"], newTodo);

이렇게 써야 업뎃이 바로 됨. 유니크 키로 맵핑된 get함수를 실행하게 된다.
setQueryData는 get함수의 파라미터를 변경해야할때 사용한다고 한다.

Delete

Before

import { useEffect, useState } from "react";
import { deleteTodo, getSpecificTodo } from "../../../api/TodoApi";
import EditMode from "../editMode/EditMode";
import { useNavigate } from "react-router-dom";

interface TodoType {
  title?: string,
  content?: string,
  createdAt?: string,
}

const TodoDetail = ({curParams, refresh, refresher}) => {
  const [edit, setEdit] = useState(false);
  const [detailTodo, setDetailTodo] = useState({});
  const {title, content, createdAt}: TodoType = detailTodo;
  const navigate = useNavigate();
  useEffect(() => {
    const fetchData = async () => {
      const res = await getSpecificTodo('/todos/', curParams);
      setDetailTodo(res.data);
    }
    fetchData();
  }, [curParams, refresh]);
  const handleDelete = (e) => {
    e.preventDefault();
    deleteTodo('/todos/', curParams)
    navigate('/');
    refresher();
  };
  
  return (
    <>
      <TodoDetailStyle.TodoDetailBox>
            <TodoDetailStyle.TodoTitle>
              <BiCircle />
              <h3>{title}</h3>
              <TodoDetailStyle.EditBox>
                <IconButton onClick={handleDelete} type="button">
                  <BsTrashFill />
                </IconButton>
                <IconButton onClick={handleUpdateMode} type="button">
                  <FaEdit />
                </IconButton>
              </TodoDetailStyle.EditBox>
            </TodoDetailStyle.TodoTitle>
            <TodoDetailStyle.TodoContent>
            <span>Date: {createdAt}</span>
              <p>{content}</p>
            </TodoDetailStyle.TodoContent>
      </TodoDetailStyle.TodoDetailBox>
    </>
  )
}

export default TodoDetail;

After

import { deleteTodo } from "../../../api/TodoApi";
import { useNavigate } from "react-router-dom";
import { useMutation, useQueryClient } from "react-query";

const TodoDetail = ({ curParams }) => {
  const navigate = useNavigate();
  const queryClient = useQueryClient();
  const { mutate } = useMutation(deleteTodo, {
    onSuccess: () => {
      queryClient.invalidateQueries(["todo"]);
    },
  });

  const handleDelete = (e) => {
    e.preventDefault();
    mutate(curParams);
    navigate("/");
  };

  return (
    <>
      <TodoDetailStyle.TodoDetailBox>
        <TodoDetailStyle.TodoTitle>
          <BiCircle />
          <h3>{title}</h3>
          <TodoDetailStyle.EditBox>
            <IconButton onClick={handleDelete} type="button">
              <BsTrashFill />
            </IconButton>
            <IconButton onClick={handleUpdateMode} type="button">
              <FaEdit />
            </IconButton>
          </TodoDetailStyle.EditBox>
        </TodoDetailStyle.TodoTitle>
        <TodoDetailStyle.TodoContent>
          <span>Date: {createdAt}</span>
          <p>{content}</p>
        </TodoDetailStyle.TodoContent>
      </TodoDetailStyle.TodoDetailBox>
    </>
};

export default TodoDetail;

리액트 쿼리를 사용하니 다른 코드에 대한 의존도가 낮아진 것 같다.
그놈의 리프레셔도 안써도 되구...

Detail Read

Before

import { useEffect, useState } from "react";
import { deleteTodo, getSpecificTodo } from "../../../api/TodoApi";
import EditMode from "../editMode/EditMode";
import { useNavigate } from "react-router-dom";

const TodoDetail = ({ curParams, refresh, refresher }) => {
  const [edit, setEdit] = useState(false);
  const [detailTodo, setDetailTodo] = useState({});
  const { title, content, createdAt }: TodoType = detailTodo;
  const navigate = useNavigate();
  useEffect(() => {
    const fetchData = async () => {
      const res = await getSpecificTodo(curParams);
      setDetailTodo(res.data);
    };
    fetchData();
  }, [curParams, refresh]);
  
  const handleUpdateMode = () => {
    setEdit(!edit);
  };

  return (
    <>
      <TodoDetailStyle.TodoDetailBox>
        {edit ? (
          <EditMode
            curParams={curParams}
            curTitle={title}
            curContent={content}
            refresher={refresher}
            handleUpdateMode={handleUpdateMode}
          />
        ) : (
          <>
            <TodoDetailStyle.TodoTitle>
              <BiCircle />
              <h3>{title}</h3>
              <TodoDetailStyle.EditBox>
                <IconButton onClick={handleDelete} type="button">
                  <BsTrashFill />
                </IconButton>
                <IconButton onClick={handleUpdateMode} type="button">
                  <FaEdit />
                </IconButton>
              </TodoDetailStyle.EditBox>
            </TodoDetailStyle.TodoTitle>
            <TodoDetailStyle.TodoContent>
              <span>Date: {createdAt}</span>
              <p>{content}</p>
            </TodoDetailStyle.TodoContent>
          </>
        )}
      </TodoDetailStyle.TodoDetailBox>
    </>
  );
};

export default TodoDetail;

After

앞서 list를 적용한 것처럼 해봤는데 값이 undefined가 나온다. 뭔가 잘못된듯;

Cannot destructure property 'title' of 'data' as it is undefined.

계속 위와 같은 에러가 났었는데 다시 살펴보니 로딩전에 구조분해할당을 해서 그런거였당... 헷
useSearchParams를 사용해서 url 파라미터를 얻을 수 있다던데
일단 useParams를 쓰고잇음.

import { useState } from "react";
import { deleteTodo, getSpecificTodo } from "../../../api/TodoApi";
import EditMode from "../editMode/EditMode";
import { useNavigate } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "react-query";

const TodoDetail = ({ curParams }) => {
  const [edit, setEdit] = useState(false);
  const navigate = useNavigate();

  const fetchDetail = async ({ queryKey }) => {
    const [_, id] = queryKey;
    const res = await getSpecificTodo(id);
    return res.data;
  };

  const { data, isLoading, isError } = useQuery(
    ["detailTodo", curParams],
    fetchDetail
  );
  
  if (isLoading) {
    return <div>is Loading...</div>;
  }

  if (isError) {
    return <div>is Error...</div>;
  }
  
  const { title, content, createdAt }: TodoType = data;
 //이부분이 문제였음!!!
  return (
    <>
      <TodoDetailStyle.TodoDetailBox>
            <TodoDetailStyle.TodoTitle>
              <BiCircle />
              <h3>{title}</h3>
              <TodoDetailStyle.EditBox>
                <IconButton onClick={handleDelete} type="button">
                  <BsTrashFill />
                </IconButton>
                <IconButton onClick={handleUpdateMode} type="button">
                  <FaEdit />
                </IconButton>
              </TodoDetailStyle.EditBox>
            </TodoDetailStyle.TodoTitle>
            <TodoDetailStyle.TodoContent>
              <span>Date: {createdAt}</span>
              <p>{content}</p>
            </TodoDetailStyle.TodoContent>
      </TodoDetailStyle.TodoDetailBox>
    </>
  );
};

export default TodoDetail;

Update

Before

import { useState } from "react";
import { updateTodo } from "../../../api/TodoApi";
import Input from "../../../common/Input/Input";
import Button from "../../../common/button/Button";
import * as EditModeStyle from './EditModeStyle';
import Textarea from "../../../common/textaea/Textarea";

const EditMode = ({curParams, refresher, curTitle, curContent, handleUpdateMode}) => {
  const [updateTodoData, setUpdateTodoData] = useState({
    title: '',
    content: ''
  });
  const {title, content} = updateTodoData;
  const handleUpdateTodo = (e) => {
    const { value, name } = e.target;
    setUpdateTodoData({
      ...updateTodoData,
      [name]: value
    });
  }
  const submitUpdateTodo = (e) => {
    e.preventDefault();
    const payload = {
      title: title ? title : curTitle,
      content: content ? content : curContent
    }
    updateTodo(curParams, payload);
    refresher();
    handleUpdateMode();
  }
  return (
    <>
      <EditModeStyle.Form onSubmit={submitUpdateTodo}>
        <EditModeStyle.EditTitle>
          <h3>Todo 수정하기</h3>
        </EditModeStyle.EditTitle>
        <div>
          <EditModeStyle.TodoTitle>
            <Input name="title" 
            id="edit Title"
            value={title ? title : curTitle} 
            onChange={handleUpdateTodo}
            type="text"
            />
          </EditModeStyle.TodoTitle>
          <Textarea name="content" 
          value={content ? content : curContent} 
          onChange={handleUpdateTodo}/>
        </div>
        <EditModeStyle.ButtonBox>
          <Button styles="cancel" onClick={handleUpdateMode} type="button">취소하기</Button>
          <Button styles="default" type="submit">수정하기</Button>
        </EditModeStyle.ButtonBox>
      </EditModeStyle.Form>
    </>
  )
}

export default EditMode;

After

funtcion is not assignable to parameter of type 'MutationKey'
useMutation은 한개의 인자만 받는다. 두개라서 계속 에러가 났음..

아 업데이트 어렵당....ㅠㅠ

아 콘솔로 찍어보고 알았다!!
{curParams, payload} 이렇게 api에 전달하는데
구조분해할당은 {todoId, parameter} 이렇게 하니까 undefined가 나와서 계속 에러가 났던것....

import { useState } from "react";
import { updateTodo } from "../../../api/TodoApi";
import { useMutation, useQueryClient } from "@tanstack/react-query";

const EditMode = ({ curParams, curTitle, curContent, handleUpdateMode }) => {
  const [updateTodoData, setUpdateTodoData] = useState({
    title: "",
    content: "",
  });
  const { title, content } = updateTodoData;
  const handleUpdateTodo = (e) => {
    const { value, name } = e.target;
    setUpdateTodoData({
      ...updateTodoData,
      [name]: value,
    });
  };

  const queryClient = useQueryClient();
  const modifyMutation = useMutation({
    mutationFn: updateTodo,
    onSuccess: (modifiedData) => {
      queryClient.setQueryData(["detailTodo", { id: curParams }], modifiedData);
      queryClient.invalidateQueries(["detailTodo"]);
    },
  });

  const submitUpdateTodo = (e) => {
    e.preventDefault();
    const todoId = curParams;
    const parameter = {
      title: title ? title : curTitle,
      content: content ? content : curContent,
    };
    modifyMutation.mutate({ todoId, parameter });
    handleUpdateMode();
  };
  return (
    <>
      <EditModeStyle.Form onSubmit={submitUpdateTodo}>
        <EditModeStyle.EditTitle>
          <h3>Todo 수정하기</h3>
        </EditModeStyle.EditTitle>
        <div>
          <EditModeStyle.TodoTitle>
            <Input
              name="title"
              id="edit Title"
              value={title ? title : curTitle}
              onChange={handleUpdateTodo}
              type="text"
            />
          </EditModeStyle.TodoTitle>
          <Textarea
            name="content"
            value={content ? content : curContent}
            onChange={handleUpdateTodo}
          />
        </div>
        <EditModeStyle.ButtonBox>
          <Button styles="cancel" onClick={handleUpdateMode} type="button">
            취소하기
          </Button>
          <Button styles="default" type="submit">
            수정하기
          </Button>
        </EditModeStyle.ButtonBox>
      </EditModeStyle.Form>
    </>
  );
};

export default EditMode;
profile
주니어 플러터 개발자의 고군분투기

0개의 댓글