인프런 커뮤니티 질문&답변

장산님의 프로필 이미지

작성한 질문수

Next + React Query로 SNS 서비스 만들기

서로 다른 컴포넌트간 query 일치하게 하기

서로 다른 컴포넌트간 query 일치하게 하기 강의중

24.08.13 21:32 작성

·

71

0

안녕하세여 제로초님
UserInfo에서 팔로우버튼을 누르면 팔로잉으로 변해야되고 다시 한번 누르면 팔로우로 변해야되는데 버튼을 누르고 새로고침을 해야지만 반영이됩니다...
팔로우 추천에서는 바로 반영이 되는데....
깃허브 ch3-2 UserInfo에 있는 코드로 가져다 써도 안되네여 ㅠㅠ

"use client";

import style from "@/app/(afterLogin)/[username]/profile.module.css";
import BackButton from "@/app/(afterLogin)/_component/BackButton";
import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
import { User } from "@/model/User";
import { getUser } from "@/app/(afterLogin)/[username]/_lib/getUser";
import cx from "classnames";
import { MouseEventHandler } from "react";
import { Session } from "@auth/core/types";

type Props = {
  username: string;
  session: Session | null;
};
export default function UserInfo({ username, session }: Props) {
  const { data: user, error } = useQuery<
    User,
    Object,
    User,
    [_1: string, _2: string]
  >({
    queryKey: ["users", username],
    queryFn: getUser,
    staleTime: 60 * 1000, // fresh -> stale, 5분이라는 기준
    gcTime: 300 * 1000,
  });
  const queryClient = useQueryClient();
  const follow = useMutation({
    mutationFn: (userId: string) => {
      console.log("follow", userId);
      return fetch(
        `${process.env.NEXT_PUBLIC_BASE_URL}/api/users/${userId}/follow`,
        {
          credentials: "include",
          method: "post",
        }
      );
    },
    onMutate(userId: string) {
      const value: User[] | undefined = queryClient.getQueryData([
        "users",
        "followRecommends",
      ]);
      if (value) {
        const index = value.findIndex((v) => v.id === userId);
        if (index > -1) {
          console.log(value, userId, index);
          const shallow = [...value];
          shallow[index] = {
            ...shallow[index],
            Followers: [{ id: session?.user?.email as string }],
            _count: {
              ...shallow[index]._count,
              Followers: shallow[index]._count?.Followers + 1,
            },
          };
          queryClient.setQueryData(["users", "followRecommends"], shallow);
        }
      }
      const value2: User | undefined = queryClient.getQueryData([
        "users",
        userId,
      ]);
      if (value2) {
        const shallow: User = {
          ...value2,
          Followers: [{ id: session?.user?.email as string }],
          _count: {
            ...value2._count,
            Followers: value2._count?.Followers + 1,
          },
        };
        queryClient.setQueryData(["users", userId], shallow);
      }
    },
    onError(error, userId: string) {
      console.error(error);
      const value: User[] | undefined = queryClient.getQueryData([
        "users",
        "followRecommends",
      ]);
      if (value) {
        const index = value.findIndex((v) => v.id === userId);
        console.log(value, userId, index);
        if (index > -1) {
          const shallow = [...value];
          shallow[index] = {
            ...shallow[index],
            Followers: shallow[index].Followers.filter(
              (v) => v.id !== session?.user?.email
            ),
            _count: {
              ...shallow[index]._count,
              Followers: shallow[index]._count?.Followers - 1,
            },
          };
          queryClient.setQueryData(["users", "followRecommends"], shallow);
        }
        const value2: User | undefined = queryClient.getQueryData([
          "users",
          userId,
        ]);
        if (value2) {
          const shallow = {
            ...value2,
            Followers: value2.Followers.filter(
              (v) => v.id !== session?.user?.email
            ),
            _count: {
              ...value2._count,
              Followers: value2._count?.Followers - 1,
            },
          };
          queryClient.setQueryData(["users", userId], shallow);
        }
      }
    },
  });
  const unfollow = useMutation({
    mutationFn: (userId: string) => {
      console.log("unfollow", userId);
      return fetch(
        `${process.env.NEXT_PUBLIC_BASE_URL}/api/users/${userId}/follow`,
        {
          credentials: "include",
          method: "delete",
        }
      );
    },
    onMutate(userId: string) {
      const value: User[] | undefined = queryClient.getQueryData([
        "users",
        "followRecommends",
      ]);
      if (value) {
        const index = value.findIndex((v) => v.id === userId);
        console.log(value, userId, index);
        if (index > -1) {
          const shallow = [...value];
          shallow[index] = {
            ...shallow[index],
            Followers: shallow[index].Followers.filter(
              (v) => v.id !== session?.user?.email
            ),
            _count: {
              ...shallow[index]._count,
              Followers: shallow[index]._count?.Followers - 1,
            },
          };
          queryClient.setQueryData(["users", "followRecommends"], shallow);
        }
        const value2: User | undefined = queryClient.getQueryData([
          "users",
          userId,
        ]);
        if (value2) {
          const shallow = {
            ...value2,
            Followers: value2.Followers.filter(
              (v) => v.id !== session?.user?.email
            ),
            _count: {
              ...value2._count,
              Followers: value2._count?.Followers - 1,
            },
          };
          queryClient.setQueryData(["users", userId], shallow);
        }
      }
    },
    onError(error, userId: string) {
      console.error(error);
      const value: User[] | undefined = queryClient.getQueryData([
        "users",
        "followRecommends",
      ]);
      if (value) {
        const index = value.findIndex((v) => v.id === userId);
        console.log(value, userId, index);
        if (index > -1) {
          const shallow = [...value];
          shallow[index] = {
            ...shallow[index],
            Followers: [{ id: session?.user?.email as string }],
            _count: {
              ...shallow[index]._count,
              Followers: shallow[index]._count?.Followers + 1,
            },
          };
          queryClient.setQueryData(["users", "followRecommends"], shallow);
        }
      }
      const value2: User | undefined = queryClient.getQueryData([
        "users",
        userId,
      ]);
      if (value2) {
        const shallow = {
          ...value2,
          Followers: [{ userId: session?.user?.email as string }],
          _count: {
            ...value2._count,
            Followers: value2._count?.Followers + 1,
          },
        };
        queryClient.setQueryData(["users", userId], shallow);
      }
    },
  });

  console.log("error");
  console.dir(error);
  if (error) {
    return (
      <>
        <div className={style.header}>
          <BackButton />
          <h3 className={style.headerTitle}>프로필</h3>
        </div>
        <div className={style.userZone}>
          <div className={style.userImage}></div>
          <div className={style.userName}>
            <div>@{username}</div>
          </div>
        </div>
        <div
          style={{
            height: 100,
            alignItems: "center",
            fontSize: 31,
            fontWeight: "bold",
            justifyContent: "center",
            display: "flex",
          }}
        >
          계정이 존재하지 않음
        </div>
      </>
    );
  }
  if (!user) {
    return null;
  }

  const followed = user.Followers?.find((v) => v.id === session?.user?.email);
  console.log(session?.user?.email, followed);

  const onFollow: MouseEventHandler<HTMLButtonElement> = (e) => {
    e.stopPropagation();
    e.preventDefault();
    console.log("follow", followed, user.id);
    if (followed) {
      unfollow.mutate(user.id);
    } else {
      follow.mutate(user.id);
    }
  };

  return (
    <>
      <div className={style.header}>
        <BackButton />
        <h3 className={style.headerTitle}>{user.nickname}</h3>
      </div>
      <div className={style.userZone}>
        <div className={style.userRow}>
          <div className={style.userImage}>
            <img src={user.image} alt={user.id} />
          </div>
          <div className={style.userName}>
            <div>{user.nickname}</div>
            <div>@{user.id}</div>
          </div>
          {user.id !== session?.user?.email && (
            <button
              onClick={onFollow}
              className={cx(style.followButton, followed && style.followed)}
            >
              {followed ? "팔로잉" : "팔로우"}
            </button>
          )}
        </div>
        <div className={style.userFollower}>
          <div>{user._count.Followers} 팔로워</div>
          &nbsp;
          <div>{user._count.Followings} 팔로우 중</div>
        </div>
      </div>
    </>
  );
}

제가 보기에는 useQuery가 제대로 작동안하는거같은데...제로초님 의견이 궁금합니다

스크린샷 2024-08-13 오후 9.30.47.png

팔로우버튼 안눌렀을때

스크린샷 2024-08-13 오후 9.31.44.png

팔로우버튼 눌렀을때

 

답변 2

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

2024. 08. 16. 10:13

답변이 조금 늦었습니다. 인프런 AI 답변이 정확해서 도움되셨기를 바랍니다. 저는 ch3-2로 했을 때 문제가 없었습니다. 다른 쪽의 쿼리키에 오타가 있는 것은 아닌지도 확인해보세요.

0

인프런 AI 인턴님의 프로필 이미지

2024. 08. 13. 21:37

안녕하세요, 인프런 AI 인턴입니다.

장산님의 경우, React Query를 사용하여 구현된 컴포넌트 간의 상태 동기화 문제를 해결하고자 합니다. 팔로우 버튼의 상태가 즉각적으로 반영되지 않는 문제는, React Query의 캐시 업데이트가 바로 반영되지 않기 때문일 수 있습니다. 주요 포인트는 useMutationonMutate 혹은 onSuccess 콜백에서 캐시를 업데이트하는 로직이 적절하게 작성되었는지, 그리고 이렇게 업데이트된 캐시가 다른 컴포넌트에 바로 반영되도록 설계가 되었는지 확인하는 것입니다.

질문과 상황이 유사한 몇 가지 답변을 참고하시면 도움이 될 것 같습니다:

  1. mutateFollower나 mutateFollowing을 사용하는 경우: 예시에서는 팔로우 상태 변경 시 캐시 업데이트 로직을 onMutate에서 처리하고 있습니다. 다른 컴포넌트에서 이 캐시를 공유하려면 동일한 query key를 사용해야 합니다. 하지만 이러한 처리만으로도 간혹 즉시 반영이 이루어지지 않는 경우가 있을 수 있습니다. 그런 경우, 상태 업데이트를 강제할 수 있는 방법을 찾아야 합니다.

새로 고침 이전에 팔로잉 팔로워 취소가 반영이 안되여에서는 mutateFollowing과 mutateFollower를 사용하여 팔로워/팔로잉 목록을 업데이트하는 방법에 대한 논의가 있습니다. 해당 답변에서 제시된 방법을 채택하거나 참고하여, 일시적으로 캐시를 수정하는 방법에 대한 이해를 도울 수 있습니다.

  1. 캐시 업데이트를 통한 동기화: 질문에서 제공된 코드에서는 팔로워 버튼의 상태가 변경되었을 때 그 변경사항을 React Query의 캐시에 반영하는 로직이 있습니다. 하지만, 이 변경사항은 새로고침을 하기 전까지 다른 컴포넌트에는 반영되지 않고 있습니다. 이는 queryClient.setQueryData를 통해 업데이트한 데이터가 누락되거나 잘못된 query key를 사용하여 업데이트가 정상적으로 진행되지 않았을 가능성이 있습니다.

위 상황에 대해 비슷한 문제를 다룬 다른 질문이나 답변을 찾지 못 했습니다. 이는 해당 문제가 고유하고 특정한 상황에 맞춘 구현 문제일 수 있으므로, React Query 공식 문서의 캐시 업데이트 방법Optimistic Updates 섹션을 참고할 것을 추천합니다.

이 경우, onMutate에서 즉각적으로 캐시를 업데이트하고, onSuccess 혹은 onError에서 API 호출 결과에 따라 캐시를 롤백하는 패턴을 적용해보세요.

질문의 세부적인 내용을 기반으로 한 답변을 제공하기에 한계가 있으나, 위에서 언급한 리소스를 통해 문제를 해결하는 데 도움이 될 수 있기를 바랍니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

장산님의 프로필 이미지

작성한 질문수

질문하기