본문 바로가기
Next.js

[TIL] 로딩 상태 구현이 안될 때 (async/await의 중요성)

by 어느새벽 2024. 8. 27.

🚨문제

: 로그인이 되어 있고 구독한 인플루언서가 있는데 렌더링 시 "로그인 정보가 없습니다."와 "구독한 인플루언서 정보가 없습니다." 안내문 모두 렌더링이 되고 마지막에 실제 정보가 뜸

 

❓고민

1. 데이터를 fetch로 가져올 때 useQuery를 쓰면 데이터를 더 빨리 가져오지 않을까 싶어 바꿔봤으나 안됨.

2. return문의 삼항연산자 로직 순서를 바꾸면 렌더링 순서도 바뀔까 싶었지만 안됨.

 

✔️해결

먼저 오류 코드를 보면

  useEffect(() => {
    const fetchData = async () => {
      if (user) {
        await getSubscribeData();
      }
      setIsLoading(false);
    };

    fetchData();
  }, [user]);

로딩 상태 자리를 잘 못 썼던게 문제였다. 

로그인정보 (user)가 있으면 구독 정보를 가져오고 바로 로딩 상태를 false로 줘야하는데 if문 바깥에 둬서 생긴 오류였다.

 

아래는 수정한 코드이다.

//Allinfluencer.tsx

"use client";

//생략

function AllInfluencers() {
  const { data: user } = useUserData();
  const [subscribeIds, setSubscribeIds] = useState<string[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const router = useRouter();

  useEffect(() => {
    const fetchData = async () => {
      if (user) {
        await getSubscribeData();
        setIsLoading(false);
      }
    };

    fetchData();
  }, [user]);

  const getSubscribeData = async () => {
    try {
      const response = await fetch(`/api/subscribe?user_id=${user.id}`);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      const otherData = data.map((d: InfSubscribe) => d.infuser_id);
      setSubscribeIds(otherData.filter((d: InfSubscribe) => d !== user.id));
    } catch (error) {
      console.log("Failed to fetch subscription data:", error);
    }
  };

 //생략

  if (isLoading) {
    return <LoadingUrr />;
  }

  return (
    <div className="w-full xl:w-[1200px] bg-[#F4F4F4] mx-auto">
      <InfGuidModal />
      <div className="w-full h-[30%] p-4 bg-[#FFFFFE]">
        <h1 className="font-bold text-lg">내가 구독중인 인플루언서</h1>
        {!user ? (
          <div className="flex h-[200px]">
            <p className="text-[#4C4F52] text-[16px] my-5 xl:text-[18px] xl:mb-[52px] mx-auto mt-[80px]">
              로그인 정보가 없습니다.
            </p>
          </div>
        ) : subscribeIds.length === 0 ? (
          <div className="flex flex-col items-center mx-auto">
            <div className="relative w-[150px] h-[100px] my-3 xl:my-[26px]">
              <Image src={emptyImg} alt="empty" fill sizes="100px xl:w-[150px]" className="mx-auto my-5 object-cover" />
            </div>
            <p className="text-[#4C4F52] text-[16px] my-6 xl:text-[18px]">현재 구독중인 인플루언서가 없습니다.</p>
          </div>
        ) : (
          <div className="w-auto flex overflow-x-auto mt-5 gap-3 scrollbar-hide p-2">
            {infUser
              ?.filter((inf) => subscribeIds.includes(inf.id))
              .map((inf) => (
                <div className="grid text-center" key={inf.id}>
                  <div className="relative w-[90px] h-[90px] mb-2 xl:w-[140px] xl:h-[140px] xl:mt-[10px]">
                    <Link href={`influencer/profile/${inf.id}`}>
                      <div className="relative w-[90px] h-[90px] xl:w-[140px] xl:h-[140px]">
                        <Image
                          src={inf.profile_url || defaultImg}
                          alt="img"
                          fill
                          sizes="90px xl:w-[140px]"
                          className="rounded-md object-cover gradient-border"
                        />
                      </div>
                    </Link>
                    <div className="absolute bottom-0.5 right-1">
                      {subscribeIds.includes(inf.id) ? (
                        <button onClick={() => cancelSubscribeHandler(inf)}>
                          <FullHeartIcon />
                        </button>
                      ) : (
                        <button onClick={(e) => subscribeHandler(e, inf)}>
                          <EmptyHeartIcon />
                        </button>
                      )}
                    </div>
                  </div>
                  <p className="text-[14px] mb-2 xl:text-[16px]">{inf.nickname}</p>
                </div>
              ))}
          </div>
        )}
      </div>
     
     //생략
     
    </div>
  );
}

export default AllInfluencers;

 

 

똑같은 문제로 메인페이지에서 구독한 정보를 보여주는 부분이 있었다. 같은 방법으로 해결해보자.

  const [isLoading, setIsLoading] = useState(true);
  
    useEffect(() => {
    if (user) {
      getSubscribeData();
      setIsLoading(false);
    }
    return;
  }, [user]);

 

원래 있던 코드에 로딩 상태를 관리 할 useState를 작성하고 초기값 true로 준 뒤 구독 데이터를 가져오는 함수 바로 아래에 로딩 상태를 넣었다. 그런데 똑같이 했음에도 안되는 것이다. 이전 컴포넌트와 비교했을 때 async/await의 유무 차이였다.

async/await가 없는 로직에서는 getSubcribeData() 함수가 전부 실행되기도 전에 setIsLoading이 실행이 돼서 로딩처리가 제대로 안된 것이었다.

async/await가 있으면 getSubscribeData()함수가 실행된 후 이후 로직이 실행되므로 로딩처리가 해결된다.

아래는 수정코드이다.

//SubInfluencer.tsx

"use client";

//생략

function SubInfluencer({ infUser }: { infUser: User[] }) {
  const { data: user } = useUserData();
  const [subscribeIds, setSubscribeIds] = useState<string[]>([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      if (user) {
        await getSubscribeData();
        setIsLoading(false);
      }
    };

    fetchData();
  }, [user]);

  const getSubscribeData = async () => {
    try {
      if (!user) return;

      const response = await fetch(`/api/subscribe?user_id=${user.id}`);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      setSubscribeIds(data.map((d: InfSubscribe) => d.infuser_id));
    } catch (error) {
      console.log("Failed to fetch subscription data:", error);
    }
  };

  if(isLoading) {
    return <p>로딩 중입니다.</p>;
  }  

  return (
    <div className="w-full h-[220px] xl:h-auto px-4 py-[26px]">
      <h2 className="font-bold text-xl mb-5 xl:text-[22px] xl:my-[26px]">내가 구독한 인플루언서</h2>
      <div className="flex flex-row overflow-x-auto flex-nowrap scrollbar">
        {!user ? (
          <p className="text-[#4C4F52] text-[16px] h-[142px] mx-auto flex items-center whitespace-nowrap">
            로그인 정보가 없습니다.
          </p>
        ) : subscribeIds.length === 0 ? (
          <p className="text-[#4C4F52] text-[16px] h-[142px] mx-auto flex items-center whitespace-nowrap">
            구독중인 인플루언서가 없습니다.
          </p>
        ) : (
          <div className="w-full flex overflow-x-auto gap-[18px] xl:gap-5 scrollbar-hide">
            {infUser
              ?.filter((inf) => subscribeIds.includes(inf.id) && inf.id !== user?.id) 
              .map((inf) => (
                <div
                  key={inf.id}
                  className="flex-shrink-0 flex flex-col items-center justify-center text-center w-[85px] xl:w-[150px]"
                >
                  <Link href={`influencer/profile/${inf.id}`}>
                    <div className="relative w-[85px] h-[85px] xl:w-[150px] xl:h-[150px] mb-2">
                      <Image
                        src={inf.profile_url || defaultImg}
                        alt="img"
                        fill
                        sizes="85px xl:150px"
                        className="rounded-md object-cover gradient-border"
                      />
                      <div className="absolute bottom-0.5 right-1.5">
                        {subscribeIds.includes(inf.id) ? (
                          <button>
                            <FullHeartIcon />
                          </button>
                        ) : (
                          <button>
                            <EmptyHeartIcon />
                          </button>
                        )}
                      </div>
                    </div>
                    <p className="text-sm text-[#4C4F52] mb-6 truncate xl:text-[16px]">{inf.nickname}</p>
                  </Link>
                </div>
              ))}
          </div>
        )}
      </div>
    </div>
  );
}

export default SubInfluencer;

 

 

 

'Next.js' 카테고리의 다른 글

[TIL] next.js Image 태그로 배경 이미지 적용하기  (0) 2024.07.31