inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

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

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

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

218

장산

작성한 질문수 46

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

팔로우버튼 눌렀을때

 

react next.js react-query next-auth msw

답변 2

0

제로초(조현영)

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

0

인프런 AI 인턴

안녕하세요, 인프런 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 호출 결과에 따라 캐시를 롤백하는 패턴을 적용해보세요.

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

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

캡처링부분 질문있습니다.

0

76

2

깃에 소스코드를 찾을 수 없습니다.

0

114

2

useInfiniteQuery promise와 react use 사용시 페이지 무한 로딩

0

98

1

import 파일 경로를 찾지 못 해서 에러가 발생합니다.

0

111

2

css 라이브러리 추천 부탁드립니다

0

141

2

팔로우 추천 목록이 빈 배열로 들어옵니다.

0

133

1

게시물 업로드 시 userId가 undefined로 들어가는 오류

0

119

1

useSuspenseQuery 사용 시 SSR 401 이슈 관련

0

173

1

리액트 쿼리 useinfinitequery 무한스크롤 구현 시 페이지가 이동할 경우 데이터가 보존되게 할 수 있나요??

0

186

3

폴링이 필요없는 이유

0

93

2

next Request Memoization과 react cache

0

110

2

seo 최적화 기준은 데이터 fetching인가요 아님 데이터 렌더링인가요?

0

84

2

next.js 서버fetch 에러 fallback ui 구현 방법

0

173

2

프레임워크 여론 파악법

0

125

2

필터옵션이 많은 페이지에서 서버 fetch를 하는게 맞는걸까요??

0

103

2

서버 fetch suspense 로 감싸고 새로고침 시 잠시 빈 화면이 노출된 후 fallback ui가 노출됩니다.

0

102

2

template.tsx 내 서버fetch 응답값과 클라이언트 컴포넌트 상태값 싱크가 맞지 않는 이슈

0

66

2

Auth.js 사용 시 authorize 함수가 호출되지 않습니다

0

132

2

next.js 에서 로그인에 관하여

0

139

1

Next의 route handler에 대한 질문이 있습니다.

0

101

2

게시판 리스트 만들때 use client를 어디서부터 집어넣어야할지 모르겠습니다

0

99

2

프라이빗 폴더를 해야 하는 이유가 모호한 것 같아요.

0

85

2

vanilla-extract 못찾는 문제

0

230

2

fetch 캐싱과 revalidate 관련

0

86

2