무작정 실시간 채팅앱 만들어보기 - (2) 로그인, 유저 검색

Pluto·2022년 5월 11일
0

Firebase에서 Github 인증 사용설정

파이어베이스에서 깃허브 인증 사용설정하는 방법은 이 글에 자세히 작성하였다.

로그인 기능 구현

로그인처리하기

  • Auth component
import React from "react";
import { provider } from "../../../firebaseConfig";
import { getAuth, signInWithPopup } from "firebase/auth";

function Auth() {
  const onClickLoginBtn = async () => {
    const auth = getAuth();

    signInWithPopup(auth, provider)
	  .catch((error) => {
		console.log(error.message);
      });
  }
  return (
    <>
      <div onClick={onClickLoginBtn}>로그인하기</div>
    </>
  )
}

export default Auth;

로그인하기 버튼을 누르면 onClickLoginBtn 함수가 실행되며 signInWithPopup을 통해 로그인 처리가 된다.

로그인 여부 파악하기

  • router.tsx
import React, { useEffect, useState } from "react";
import { BrowserRouter } from "react-router-dom";
import Auth from "./pages/auth";
import Main from "./pages/main";
import { getAuth, onAuthStateChanged } from "firebase/auth";

function Router() {
  const [isLogin, setIsLogin] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const auth = getAuth();
    onAuthStateChanged(auth, (user) => {
      if (user) {
        console.log(user); // console.log
        setIsLogin(true);
        setIsLoading(false);
      } else {
		alert('로그인 해주세요');
        setIsLogin(false);
        setIsLoading(false);
      }
    });
  }, []);

  return (
    <BrowserRouter>
        {isLoading ? 'loading...' : isLogin ? <Main userInfoData={userInfoData} /> : <Auth />}
    </BrowserRouter>
  );
};

export default Router;

useEffect를 통해 렌더링될때마다 onAuthStateChanged함수로 로그인 여부를 파악하고, 로그인 여부에따라 Auth, Main컴포넌트가 다르게 화면에 뜨게된다.

  • 문제 해결
    isLogin 값에 따라 페이지가 변경되도록 구현 (로그인 x: Auth페이지, 로그인 o: Main페이지)하였다.
    그런데 항상 isLogin값은 false이므로 만약 로그인 상태이다 하더라도 useEffect가 완료되기 전에는 Auth페이지가 먼저 뜬 다음 Main페이지가 뜨게되는 문제점이 있었다. 그래서 useState훅을 추가해서 로딩 중일때는 로딩 중 문구가 뜨고, 함수가 실행된 다음 로딩 완료 상태가 되도록 구현하였다.

로그아웃

  • Main Component
import React from "react";
import { getAuth, signOut } from "firebase/auth";

function Main() {
  const onClickLogoutBtn = () => {
    const auth = getAuth();
    signOut(auth).then(() => {
      alert('로그아웃 되었습니다.');
    }).catch((error) => {
      console.log(error);
      alert(error);
  	});
  }
  
  return (
    <div className="LogoutBtn" onClick={onClickLogoutBtn}>로그아웃하기</div>
  );
}

유저 검색기능

유저정보 firestore에 저장하기

초기 구상에서 유저들을 검색하여 친구 추가를 할 수 있도록 구상했는데, 검색하려면 우선 유저 정보를 데이터베이스에 저장할 필요가 있었다. 그래서 로그인시 유저 정보를 저장하는 로직을 구현하였다.

  • Auth component
import React from "react";
import { provider, db } from "../../../firebaseConfig";
import { getAuth, signInWithPopup } from "firebase/auth";
import { doc, setDoc } from "firebase/firestore";

function Auth() {
  const onClickLoginBtn = async () => {
    const auth = getAuth();

    signInWithPopup(auth, provider)
	  .then(async (result) => {
        const { uid, displayName, photoURL } = result.user;
        await setDoc(doc(db, "Users", `${uid}`), {
          uid: `${uid}`,
          userName: `${displayName}`,
          photoURL: `${photoURL}`
        });
      ...

로그인시 result에서 필요한 유저 정보를 가져와 선언한 후, "Users" Doc에 유저 컬렉션을 생성하여 정보를 객체형태로 저장한다.

유저 검색기능 추가하기

  • Main component
...

export interface UserData {
  uid?: string;
  userName?: string;
  photoURL?: string;
}

function Main({userInfoData}: {userInfoData: UserData}) {
  const [findUser, setFindUser] = useState<string>('');
  const [userData, setUserData] = useState<Array<UserData>>([]);
  const [isFindUserFinished, setIsFindUserFinished] = useState<boolean>(false);

  // 유저검색
  const onChangeFindUserInput = (e: React.FormEvent<HTMLInputElement>) => {
    const { value } = e.target as HTMLInputElement;
    setFindUser(value);
  }
  const onSubmitFindUser = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const q = query(collection(db, "Users"), where("userName", "==", findUser));
    const querySnapshot = await getDocs(q);
    setUserData([]); // 초기화시키고 forEach문 동작(중복방지)
    querySnapshot.forEach((doc) => {
      const {userName, photoURL, uid} = doc.data();
      const data: UserData = {uid, userName, photoURL};
      setUserData(arr => arr ? [...arr, data] : [data]);
    });
    setIsFindUserFinished(true);
  }

  return (
    <>
      <div>
        <form onSubmit={onSubmitFindUser}>
        <div className="FindUserInputBox">
          <input className="FindUserInput" 
  			     value={findUser}
  				 onChange={onChangeFindUserInput} 
  				 type="text"
  				 placeholder="유저를 검색하세요"
  		  />
          <button type="submit">
            <SearchIcon className="FindUserInputIcon" />
          </button>
        </div>
        </form>
      </div>
      <div>
        {userData.length || !isFindUserFinished
          ? userData.map(data => <UserList key={data.uid} data={data}/>)
          : '해당 유저정보를 찾을수없습니다.'}
      </div>
   	  ...
    </>
  )
}

export default Main;

가져올 UserData의 타입을 선언하고, 검색한 이름이 일치하는 데이터를 조건문을 통해 firestore에서 가져온다. 가져온 데이터는 useState Hook을 이용해 setUserData로 리스트 형식으로 가져온 후, userData.map을 통해 유저정보를 화면에 표시한다.

  • 문제 해결

    forEach문으로 데이터를 가져올 때 검색을 계속 누르면 userData 배열에 결과값이 계속 추가되어 데이터가 중복되어 추가되는 문제가 있었다. 그래서 가져오기 전 배열을 초기화하도록 수정하였다.

    유저 정보가 없으면 (빈 배열 [ ]) 유저 정보가 없다는 문구를 띄우는데, 검색하기 전에도 기본으로 빈 배열이기 때문에 항상 문구가 띄워진다는 문제점이 있었다. 이를 해결하기 위해 유저 검색이 끝났을때 true를 설정하는 훅을 작성하여 유저검색을 하지 않았을때는 아무 문구도 띄우지 않도록하는 논리식을 작성하여 추가하였다.

  • 결과물

profile
주니어 프론트엔드 개발자입니다.

0개의 댓글