[TIL] 10월 8일 refetchQuery 보완방법

useMutation을 사용하고 바뀐내용을 다시 받아오기 위해서 refetchQuery라는것을 사용했다.

댓글등록 완료후 등록된 댓글내용을 새로고침하지 않아도 자동적으로 받아오는 기능이다.

리패치쿼리 예시

async function onClickComment() {
    await createBoardComment({
      variables: {
        boardId: String(router.query.BoardsDetailPage),
        createBoardCommentInput: {
          writer: writer,
          contents: contents,
          password: password,
          rating: myStar,
        },
      },
      refetchQueries: [
        {
          query: FETCH_BOARD_COMMENTS,
          variables: { boardId: router.query.BoardsDetailPage },
        },
      ],
    });
  }

이렇게 리패치쿼리를 이용함으로써 새로고침없이 바로바로 업데이트되는것처럼 화면을 구현할수 있었다.

리패치쿼리의 문제점

리패치쿼리를 이용하는 부분을 자세히 보면

refetchQueries: [
        {
          query: FETCH_BOARD_COMMENTS,
          variables: { boardId: router.query.BoardsDetailPage },
        },

FETCH_BOARD_COMENTS를 이용해 전체를 다시 불러온다.

즉 댓글 한개의 변경된부분을 보여주기 위해 전체를 다시 그리는 행동을 하는것이다.

리패치쿼리는 전체를 다시 불러오기때문에 효율성이 좋지 않다.

여러가지 쿼리의 방식

쿼리는 여러가지 방식이 있고 상황에 따라 알맞는 쿼리를 사용할때 효율성이 가장 좋다.

1.useQuery(컴포넌트가 만들어지면 자동으로 쿼리실행)
2.useLazyQuery(잠시 기다렸다 쿼리실행)
3.useApolloClient(원하는 상황에서 쿼리 실행)

useApolloClient

로그인페이지가 따로 없이 로그인시 바로 화면이 바뀌는 방식의 페이지가 있다고 가정해보면 로그인 이후 리패치쿼리를 이용해 페이지를 다시 그릴수 있다.

하지만 리패치쿼리를 이용할시 로그인부분만 바뀌면 되는데 바뀐내용이 없는 부분까지 다시 그려진다.

이때 리패치쿼리를 대신해 useApolloClient를 이용할수 있다.

useApolloClient 전체코드

import { useForm } from "react-hook-form";
import styled from "@emotion/styled";
import { gql, useApolloClient, useMutation } from "@apollo/client";
import { useContext } from "react";
import { GlobalContext } from "../_app";
const LOGIN_USER = gql`
  mutation loginUser($email: String!, $password: String!) {
    loginUser(email: $email, password: $password) {
      accessToken
    }
  }
`;
const FETCH_USER_LOGGED_IN = gql`
  query fetchUserLoggedIn {
    fetchUserLoggedIn {
      email
      name
      picture
    }
  }
`;
export default function UseApolloClientPage() {
  const { setAccessToken, setUserInfo, userInfo } = useContext(GlobalContext);
  const [loginUser] = useMutation(LOGIN_USER);
  const client = useApolloClient();
  const { handleSubmit, register } = useForm();
  async function onClickLogin(data) {
    // 로그인 처리하기
    const result = await loginUser({
      variables: {
        email: data.myEmail,
        password: data.myPassword,
      },
    });
    const accessToken = result.data.loginUser.accessToken;
    const resultUserInfo = await client.query({
      query: FETCH_USER_LOGGED_IN,
      context: {
        headers: {
          authorization: `Bearer ${accessToken}`,
        },
      },
    });
    const userInfo = resultUserInfo.data.fetchUserLoggedIn;
    setAccessToken(accessToken);
    setUserInfo(userInfo);
  }
  // context, 강제로 accessToken 집어넣으려고 작성함
  return (
    <>
      {userInfo.email ? (
        `${userInfo.name}님 환영합니다.`
      ) : (
        <form onSubmit={handleSubmit(onClickLogin)}>
          이메일 : <input type="text" {...register("myEmail")} />
          비밀번호 : <input type="text" {...register("myPassword")} />
          <button>로그인하기</button>
          {/* <button type="button">버튼</button> */}
        </form>
      )}
    </>
  );
}

기본적인 로그인페이지를 hook-form 라이브러리를 이용해서 구현한 페이지 이다.

refetchQuery 대신 아래와같이 작성하였다.

const accessToken = result.data.loginUser.accessToken;
    const resultUserInfo = await client.query({
      query: FETCH_USER_LOGGED_IN,
      context: {
        headers: {
          authorization: `Bearer ${accessToken}`,
        },
      },
    });
    const userInfo = resultUserInfo.data.fetchUserLoggedIn;
    setAccessToken(accessToken);
    setUserInfo(userInfo);

accessToken에 loginUser의 accessToken을 저장시킨 후 FETCH_USER_LOGGED_IN의 쿼리를 이용해 context를 수정해주는 방식이다.

useApolloClient를 이용하면 리패치쿼리 없이 바로 변경되게 구현할수 있다.

ApolloCacheState

게시글을 삭제할때 리패치쿼리를 이용해 삭제된 게시글을 제거할수 있다.

하지만 이방식역시 바뀐내용만 처리되는것이 아니라 전체다 다시그려지게되어 효율성이 저하될수 있다.

CacheState값을 직접 삭제해주는 방식으로 리패치 없이 구현이 가능하다.

ApolloCacheState 전체코드

import { gql, useMutation, useQuery } from "@apollo/client";

const FETCH_BOARDS = gql`
  query fetchBoards {
    fetchBoards {
      _id
      writer
      title
      contents
    }
  }
`;
const DELETE_BOARD = gql`
  mutation deleteBoard($boardId: ID!) {
    deleteBoard(boardId: $boardId)
  }
`;
const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
      writer
      title
      contents
    }
  }
`;
export default function ApolloCacheStatePage() {
  const [deleteBoard] = useMutation(DELETE_BOARD);
  const { data } = useQuery(FETCH_BOARDS);
  const [createBoard] = useMutation(CREATE_BOARD);
  const onClickDelete = (boardId) => async () => {
    // boardId
    await deleteBoard({
      variables: {
        boardId: boardId,
      },
      // refetchQueries:[fetchBoards] 10개를 다시 리패치해서 효율성 안좋음
      // variables요청이 끝나면 update가 실행됨
      // {data} 응답받는 ID!가 들어옴
      update(cache, { data }) {
        const deletedId = data.deleteBoard;
        cache.modify({
          fields: {
            fetchBoards: (prev, { readField }) => {
              // prev는 기존의 10개
              // 기존의 패치보드 10개에서, 지금 삭제된 id를 제외한 9개를 만들고
              // 그렇게 만들어진 9개의 새로운 패치보드를 리턴하여 덮어씌우기
              const newFetchBoards = prev.filter((el) => {
                return readField("_id", el) !== deletedId;
              });
              return [...newFetchBoards];
              // 기존에 있던 10개를 덮어씌워줘
            },
          },
        });
      },
    });
  };
  const onClickCreate = () => {
    createBoard({
      variables: {
        createBoardInput: {
          writer: "철수",
          password: "1234",
          title: "메롱메롱",
          contents: "메롱초등학교",
        },
      },
      update(cache, { data }) {
        cache.modify({
          fields: {
            fetchBoards: (prev) => {
              // 추가된 크리에이트보드 결과물과 이전의 10개를 합쳐서 11개를 돌려주기

              return [data.createBoard, ...prev];
            },
          },
        });
      },
    });
  };
  return (
    <>
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span>{el.writer}</span>
          <span>{el.title}</span>
          <span>{el.contents}</span>
          <button onClick={onClickDelete(el._id)}>삭제하기</button>
        </div>
      ))}
      <button onClick={onClickCreate}>등록하기</button>
    </>
  );
}

update를 이용해 리패치쿼리의 기능을 대신한다.

 update(cache, { data }) {
        const deletedId = data.deleteBoard;
        cache.modify({
          fields: {
            fetchBoards: (prev, { readField }) => {
              // prev는 기존의 10개
              // 기존의 패치보드 10개에서, 지금 삭제된 id를 제외한 9개를 만들고
              // 그렇게 만들어진 9개의 새로운 패치보드를 리턴하여 덮어씌우기
              const newFetchBoards = prev.filter((el) => {
                return readField("_id", el) !== deletedId;
              });
              return [...newFetchBoards];
              // 기존에 있던 10개를 덮어씌워줘

좋은 웹페이지 즐겨찾기