React

[React] useEffect 안에서 비동기 함수 사용하기

수방방 2024. 7. 29. 01:24

📍 useEffect 안에 비동기 함수 사용하기

  • useEffect 훅은 반환값으로 클린업(clean-up) 함수를 반환할 수 있습니다.
    • 이 클린업 함수는 컴포넌트가 언마운트되거나 의존성 배열이 변경될 때 실행됩니다.
    • 클린업 함수는 자원을 해제하거나, 구독을 해지하는 등의 작업을 수행하는 데 유용합니다.
  • 반면, async 함수는 항상 Promise 객체를 반환합니다.
    • await 키워드는 async 함수 내부에서 사용되며, Promise가 해결될 때까지 함수의 실행을 일시 중지합니다.
    • Promise가 해결되면, await는 해당 Promise의 결과 값을 반환합니다.
  • `useEffect`는 직접적으로 비동기 함수를 반환할 수 없기 때문에, 비동기 작업을 처리할 때는 내부에 즉시 실행 함수(IIFE)를 사용하거나, 별도의 비동기 함수를 정의하고 호출하는 방식을 사용합니다.
  • 예를 들어, 다음은 Supabase를 사용하여 로그인된 사용자 정보를 가져오는 방법입니다.
useEffect(() => {
    (async () => {
      const user = await supabase.auth.getUser();
      setUserResponse(user);
    })();
}, []);
  • 이 코드에서 `useEffect`는 컴포넌트가 마운트될 때 실행됩니다.
  • 훅 내부에서 정의된 즉시 실행 함수(IIFE)는 비동기 함수로, `supabase.auth.getUser` 를 호출하여 사용자의 로그인 정보를 가져옵니다.
  • 이 비동기 함수는 Promise 를 반환하며, `await` 키워드를 사용해 비동기 호출의 결과를 기다립니다.

 

📍 왜 useEffect 안에 비동기 함수를 사용했을까?

import Input from '@/components/Input';
import { createClient } from '@/utils/supabase/client';
import { UserResponse } from '@supabase/supabase-js';
import { useRouter } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';

const supabase = createClient();

export default function Admin() {
  const router = useRouter();
  const [userResponse, setUserResponse] = useState<UserResponse>();
  const emailRef = useRef<HTMLInputElement>(null);
  const passwordRef = useRef<HTMLInputElement>(null);

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const response = await supabase.auth.signInWithPassword({
      email: emailRef.current?.value ?? '',
      password: passwordRef.current?.value ?? '',
    });

    if (!response.data.user) {
      return alert('로그인에 실패하였습니다.');
    }

    router.refresh(); // 현재 라우터를 새로고침, 서버에 새 요청을 보내고 데이터 요청을 다시 가져오며, 서버 컴포넌트를 다시 렌더링
  };

  useEffect(() => {
    (async () => {
      const user = await supabase.auth.getUser();
      setUserResponse(user);
    })();
  }, []);

  return (
    <div className="container mx-auto flex flex-col px-4 pb-20 pt-12">
      {!!userResponse?.data.user ? (
        <>
          <div className="flex flex-col gap-2">
            <div className="mb-8">
              <b>{userResponse.data.user.email}</b> 님으로 로그인하셨습니다.
            </div>
          </div>
          <button
            type="button"
            className="w-full rounded-md bg-gray-800 py-2 text-white"
            onClick={() => router.push('/write')}
          >
            글 쓰러 가기
          </button>
          <button
            type="button"
            className="mt-4 w-full rounded-md bg-gray-800 py-2 text-white"
            onClick={() => {
              supabase.auth.signOut();
              router.push('/');
            }}
          >
            로그아웃
          </button>
        </>
      ) : (
        <div className="flex flex-col gap-8">
          <h1 className="text-2xl font-medium">관리자 로그인</h1>
          <form onSubmit={handleSubmit}>
            <div className="flex flex-col gap-3">
              <Input type="text" placeholder="이메일" ref={emailRef} />
              <Input type="password" placeholder="비밀번호" ref={passwordRef} />
            </div>
            <button
              type="submit"
              className="mt-4 w-full rounded-md bg-gray-800 py-2 text-white"
            >
              로그인
            </button>
          </form>
        </div>
      )}
    </div>
  );
}
  • 위 코드에서 보시다시피, `useEffect` 안에 `getUser()`를 호출하는 이유는 컴포넌트가 마운트될 때 사용자의 로그인 상태를 확인하고 그에 따라 적절한 UI를 렌더링하기 위함입니다.
  • 페이지가 로드될 때, `useEffect`가 실행되어 현재 로그인된 사용자의 정보를 가져와 상태를 업데이트합니다. 
  • `handleSubmit` 함수는 로그인 폼 제출 시 호출되며, 로그인 성공 후 `router.refresh()`를 통해 페이지를 새로고침합니다.
    • 이 과정에서 컴포넌트가 다시 마운트되어 `useEffect`가 다시 실행 되고, 최신 사용자 정보를 가져옵니다.