[정리] 게시글 CRUD - useMutation

­chae-zero·2023년 5월 30일
0

1. useMutation 커스텀 훅과 mutateAsync 활용

// useUpdateMutation

export const useUpdateMutation = () => {
  
  // 1. useRouter : Next.js의 라우터 객체 가져오기
  const router = useRouter();

  // 2. useQueryClient hook : query client 객체 가져오기
  const queryClient = useQueryClient();

  // 3. useMutation : mutationFn, onSuccess, onError, onSettled 객체 반환
  return useMutation({
    // 4. updatePostDetail : 백 서버에 포스트 데이터 업데이트 요청
    mutationFn: updatePostDetail,
    
    // 5. queryClient : 캐시된 데이터 무효화 후 업데이트된 포스트의 상세 페이지로 redirect
    onSuccess: async (_, variables) => {
      await queryClient.invalidateQueries({ queryKey: ["updatedpost"] });

      router.push(`/posts/${variables.postId}`);
    },
    
    // mutation 작업이 실패했을 때
    onError: (err) => {
      console.error(err);
    },
    // mutation 작업이 완료되었을 때
    onSettled: () => {
      console.log("완료");
    },
  });
> };

  • eMutation hook은 mutation 작업을 수행하고, 성공/실패/완료 상태를 처리하기 위한 콜백 함수를 제공
  • 이렇게 커스텀 훅을 따로 작성해놓고 추후에 필요한 곳에 import 하기만 하면, 코드를 보다 간결하고 깔끔하게 유지할 수 있음.

// 게시글 수정 페이지 (EditPost)

export default function EditPost({ id }: { id: string }) {
  // pwdChecked state 생성
  const [pwdChecked, setPwdChecked] = useState(true);

  // 기존 게시글 데이터 불러오기
  const { data: oldPost, isLoading: isReadLoading } = useQuery(
    ["postDetail", id],
    async () => {
      const response = await axios.get(`/posts/${id}`);
      return response.data;
    }
  );

  // useUpdateMutation 커스텀 훅 가져오기 (구조분해 할당)
  const { data, isLoading, mutate, mutateAsync } = useUpdateMutation();

  const {
    register,
    handleSubmit,
    formState: { errors },
    setError,
    setValue,
    control,
  } = useForm<IPostForm>({
    defaultValues: {
      nickname: oldPost?.nickname || "",
      title: oldPost?.title || "",
      content: oldPost?.content || "",
    },
  });

  const onValid: SubmitHandler<IPostForm> = async (data) => {
    if (!data.nickname || !data.title || !data.content) {
      const errMsg: { [key: string]: string } = {};

      if (!data.nickname) errMsg.nickname = "이름 또는 닉네임을 입력해 주세요.";
      if (!data.title) errMsg.title = "제목을 입력해 주세요.";
      if (!data.content) errMsg.content = "내용을 입력해 주세요.";
      const setErrors = (errors: Record<string, string>) => {
        Object.entries(errors).forEach(([key, value]) => {
          setError(key as "nickname" | "title" | "content", {
            message: value,
            type: "required",
          });
        });
      };
      setErrors(errMsg);
      return;
    }

    const updateData: IUpdatePostForm = {
      postId: id as string,
      ...data,
    };
    await mutateAsync(updateData);
  };

  return (
    <>
      {isReadLoading && <Loading />}
      {pwdChecked ? (
        <PostUpdatePwd id={id} setPwdChecked={setPwdChecked} />
      ) : (
        oldPost && (
          <div className="w-full px-4"> 
		(이하 생략)

에러 경험

비밀번호 체크 시 입력 받은 비밀번호가 부모 컴포넌트인 EditPost()에 전달되지 않아, data.password 가 undefined 상태로 put 되어 axios 400 에러가 발생했습니다.

이를 해결하기 위해 Props를 이용하여 함수를 전달하고 호출하여, PostUpdatePwd 컴포넌트에서 입력받은 password를 부모 컴포넌트에서 사용할 수 있도록 코드를 수정했습니다.

  1. EditPost에서 password의 상태를 관리할 수 있도록 state를 생성하고 handlePassword 함수를 만들어서 PostUpdatePwd에 Props로 전달합니다.
import { useState } from "react";
import PostUpdatePwd from "./PostUpdatePwd";

function ParentComponent() {
  const [password, setPassword] = useState("");

  const handlePassword = (password: string) => {
    setPassword(password);
  };

  return (
    <div>
      <PostUpdatePwd id="1" setPwdChecked={() => {}} handlePassword={handlePassword} />
      <div>{password}</div>
    </div>
  );
}
  1. 전달 받은 handlePassword 함수를 활용해 사용합니다. 이때 interface를 활용해 PostUpdatePwdProps의 타입을 지정해줍니다.
interface PostUpdatePwdProps {
  id: string;
  setPwdChecked: Dispatch<SetStateAction<boolean>>;
  handlePassword: (password: string) => void;
}

export default function PostUpdatePwd({
  id,
  setPwdChecked,
  handlePassword,
}: PostUpdatePwdProps) {
  // ...

  const onValid: SubmitHandler<IUpdatePostCheckForm> = async (data) => {
    // ...

    if (isSuccess) {
      handlePassword(data.password); // 부모 컴포넌트에서 전달한 함수를 호출해 'password'의 상태를 입력 받은 password로 전환
      setPwdChecked(false);
    } else {
      // ...
    }
  };

  // ...
}

위와 같이 Props와 함수를 이용하여 자식 컴포넌트에서 입력 받은 비밀번호를 부모 컴포넌트의 함수를 호출해 활용할 수 있습니다.

profile
사람 재미를 아는 길잡이가 될래요

0개의 댓글