[project] ๐ŸŽฌMovyes: ์˜ํ™” ์ปค๋ฎค๋‹ˆํ‹ฐ React ํ”„๋กœ์ ํŠธ - ๋กœ๊ทธ์ธ

์˜ค์˜ค๊ตฌยท2023๋…„ 1์›” 2์ผ
0

๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ

์ œ์ผ ๋จผ์ € ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ์€ ๋กœ๊ทธ์ธ!
๊ฐ„๋‹จํ•˜๊ฒŒ ๋กœ๊ทธ์ธ์ด๋ผ๊ณ ๋งŒ ๋งํ•˜์ง€๋งŒ ๋ผ์šฐํŒ…์ฒ˜๋ฆฌ๋ถ€ํ„ฐ ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ๋“ฑ ์‚ฌ์šฉ์ž ์ธ์ฆ๊ณผ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ•œ๋‹ค

์„ธ๋ถ€ ๊ธฐ๋Šฅ ์ •๋ฆฌ

  1. ํšŒ์›๊ฐ€์ž…
    • ์„ฑ๊ณตํ•˜๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
    • ์‹คํŒจํ•˜๋ฉด ํ•˜๋‹จ์— ์—๋Ÿฌ ๋ฌธ๊ตฌ ์ถœ๋ ฅ
  2. ๋กœ๊ทธ์ธ
    • ์„ฑ๊ณตํ•˜๋ฉด Home ์ด๋™
    • ์‹คํŒจํ•˜๋ฉด ํ•˜๋‹จ์— ์—๋Ÿฌ ๋ฌธ๊ตฌ ์ถœ๋ ฅ
  3. ๋กœ๊ทธ์•„์›ƒ
  4. user ์ธ์ฆ
    • ๋กœ๊ทธ์ธํ•˜๋ฉด user ์ •๋ณด ์ €์žฅ
    • ๋ธŒ๋ผ์šฐ์ € ๋‹ซ์œผ๋ฉด user ์ •๋ณด ์‚ญ์ œ

์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐฉ๋ฒ•์œผ๋กœ firebase api๋Š” jwt๋ฅผ ์ง€์›ํ•œ๋‹ค. ์‚ฌ์‹ค ์„ธ์…˜ ์ฟ ํ‚ค ๋ฐฉ์‹์„ ํ•œ๋ฒˆ ์‚ฌ์šฉํ•ด ๋ณด๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ ์•„์‰ฝ๊ฒŒ๋„ ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ์ค„ ๋ชจ๋ฅด๊ณ  firebase api๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ž…์žฅ์ด๋ผ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์–ด๋ ต๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

์„ธ์…˜ ์ฟ ํ‚ค๋กœ ๋งŒ๋“ค๊ณ ์‹ถ์€ ์ด์œ ๋Š” jwt๋ฅผ ๋‚จ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋Š˜ firebase auth๋งŒ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋‹ˆ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ jwt ๋งŒ ์‚ฌ์šฉํ•˜๊ฒŒ ๋์—ˆ๋Š”๋ฐ, ์„ธ์…˜ ์ฟ ํ‚ค๋„ ํ›Œ๋ฅญํ•œ ์œ ์ € ์ธ์ฆ ๋ฐฉ์‹์ด๋ผ๊ณ  ๋“ค์—ˆ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์™œ jwt๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€๋„ ๋ชจ๋ฅด๋Š” ์ฑ„ ๊ณ„์† ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์ฐœ์ฐœํ•œ ๊ธฐ๋ถ„์ด ๋“ค์—ˆ๋‹ค. ์ง€๊ธˆ์€ ์–ด์ฉ”์ˆ˜์—†์ด jwt๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ ํ›„์— node.js๋ฅผ ๊ณต๋ถ€ํ•˜๊ณ  ๋ฐฑ์—”๋“œ์ชฝ์„ ๋งŒ์ง€๊ฒŒ ๋œ๋‹ค๋ฉด ๊ผญ ์„ธ์…˜ ์ฟ ํ‚ค ๋ฐฉ์‹์œผ๋กœ ๋ฆฌํŒฉํ† ๋ง ํ•ด ๋ณผ ๊ฒƒ์ด๋‹ค.


์•„ํ‚คํ…์ฒ˜

ํด๋” ๊ตฌ์กฐ

๐Ÿ“ฆsrc
 โ”ฃ ๐Ÿ“‚api
 โ”ƒ โ”— ๐Ÿ“œfirebase.jsx // firebase api ๋ชจ์Œ
 โ”ฃ ๐Ÿ“‚components
 โ”ƒ โ”— ๐Ÿ“œSignForm.jsx // ๋กœ๊ทธ์ธ,ํšŒ์›๊ฐ€์ž… form
 โ”ฃ ๐Ÿ“‚pages
 โ”ƒ โ”ฃ ๐Ÿ“œLogin.jsx // ๋กœ๊ทธ์ธ
 โ”ƒ โ”— ๐Ÿ“œSignUp.jsx //ํšŒ์›๊ฐ€์ž…
 โ”ฃ ๐Ÿ“‚store
 โ”ƒ โ”— ๐Ÿ“œAuthContext.jsx //์œ ์ € ์ธ์ฆ ์ •๋ณด

๋ผ์šฐํŒ…

path: "/sign_in", element: <Login />
path: "/sign_up", element: <SignUp />

๊ตฌํ˜„

SignForm

Login, SignUp ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ isLogin props๋ฅผ ๋ฐ›์•„ true์ด๋ฉด ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ, false์ด๋ฉด ๊ฐ€์ž… ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•œ๋‹ค.

import React, { useRef, useState } from "react";
import { useNavigate } from "react-router";
import { createUser, loginUser } from "../api/firebase";
import Button from "./ui/Button";

export default function SignForm({ isLogin }) {
  const [user, setUser] = useState({});
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  const emailRef = useRef();
  const passwordRef = useRef();

  const btnText = isLogin ? "๋กœ๊ทธ์ธ" : "๊ฐ€์ž…ํ•˜๊ธฐ";

  const navigate = useNavigate();

  const onChangeHandler = (e) => {
    const { name, value } = e.target;
    setUser((state) => ({ ...state, [name]: value }));
  };

  const onSubmitHandler = async (e) => {
    e.preventDefault();
    setIsLoading(true);

    const response = await (isLogin
      ? loginUser(user, setError)
      : createUser(user, setError));

    response && (isLogin ? navigate("/") : navigate("/login"));
    setIsLoading(false);
    setUser({});
    emailRef.current.focus();
  };

  return (
    <form onSubmit={onSubmitHandler}>
      <input
        ref={emailRef}
        type="email"
        name="email"
        value={user.email || ""}
        onChange={onChangeHandler}
        autoComplete="off"
        placeholder="์•„์ด๋””"
        required
      />
      <input
        ref={passwordRef}
        type="password"
        name="password"
        value={user.password || ""}
        onChange={onChangeHandler}
        autoComplete="off"
        placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ"
        minLength="6"
        required
      />
      {!isLoading && <Button text={btnText} />}
      {isLoading && <p>Sending request...</p>}
      {error && <p>{error}</p>}
    </form>
  );
}

firebase API

ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ์€ ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰์ด ๋๋‚˜๋ฉด ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
์ธ์ฆ ์ƒํƒœ๋Š” ์„ธ์…˜์ด ์ข…๋ฃŒ๋˜๋ฉด ์‚ญ์ œ๋œ๋‹ค

...

// ํšŒ์›๊ฐ€์ž…
export const createUser = async ({ email, password }, callback) => {
  return createUserWithEmailAndPassword(auth, email, password)
    .then((res) => {
      alert("๊ฐ€์ž…์™„๋ฃŒ");
      return res;
    })
    .catch((error) => callback(`๊ฐ€์ž…์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ${error.code}`));
};

// ๋กœ๊ทธ์ธ
export const loginUser = async ({ email, password }, callback) => {
  await setPersistence(auth, browserSessionPersistence);

  return await signInWithEmailAndPassword(auth, email, password)
    .then((res) => res)
    .catch((error) => callback(`๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ${error.code}`));
};

// ๋กœ๊ทธ์•„์›ƒ
export const logoutUser = () => {
  signOut(auth);
};

// ์œ ์ € ๊ด€์ฐฐ
export const onUserStateChange = (callback) => {
  onAuthStateChanged(auth, (user) => {
    callback(user);
  });
};

AuthContext

AuthContext์—์„œ๋Š” user ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ์ธ์ฆ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด์„œ ํ˜„์žฌ ๋กœ๊ทธ์ธ ์ค‘์ธ์ง€ ์•„๋‹Œ์ง€ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ค€๋‹ค. context ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•œ useContext ์ฝ”๋“œ๋ฅผ useAuthContext๋กœ ๋งŒ๋“ค์–ด์„œ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ตœ๋Œ€ํ•œ ๊ฐ„ํŽธํ•˜๊ฒŒ context ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

import { createContext, useContext, useEffect, useState } from "react";
import { onUserStateChange } from "../api/firebase";

const AuthContext = createContext({
  user: {},
  isLoggedIn: false,
});

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState();

  const contextValue = {
    user,
    isLoggedIn: !!user,
  };

  useEffect(() => {
    onUserStateChange((user) => setUser(user));
  }, []);

  return (
    <AuthContext.Provider value={{ ...contextValue }}>
      {children}
    </AuthContext.Provider>
  );
};

export function useAuthContext() {
  return useContext(AuthContext);
}

ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

1. ๊ฐ€์ž…ํ•  ๋•Œ 400 ์—๋Ÿฌ ๋ฐœ์ƒ

POST https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyAwJOXTlirqOnTBtvnJ_gJ09C7brL29lEM 400

์˜คํƒ€๋กœ์ธํ•ด email์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค์ง€ ๋ชปํ•ด์„œ ๋ฐœ์ƒํ•จ. ์˜คํƒ€ ์ˆ˜์ • ํ›„ ํ•ด๊ฒฐ


๊ณ ๋ฏผํ•œ ๊ฒƒ

1. firebase API๋ฅผ ์–ด๋””์„œ ํ˜ธ์ถœํ•  ๊ฒƒ์ธ๊ฐ€

์ปดํฌ๋„ŒํŠธ์—์„œ api๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ ํ•  ๊ฒƒ์ธ์ง€, api๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” context๋‚˜ redux๋ฅผ ๋งŒ๋“ค์–ด ๊ฐ„์ ‘ ํ˜ธ์ถœ ํ•  ๊ฒƒ์ธ์ง€ ๊ณ ๋ฏผํ–ˆ๋‹ค. ์ผ๋‹จ์€ auth api๋Š” SignForm ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ๊ตณ์ด context, redux๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ๋กœ ํ–ˆ๊ณ , user ์ธ์ฆ ์ •๋ณด๋Š” ํ”„๋กœ์ ํŠธ ์ „์—ญ์ ์œผ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์ด๊ธฐ ๋•Œ๋ฌธ์— context๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ SignForm ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ๊ฐ€ ์ข€ ๋”๋Ÿฌ์›Œ๋ณด์ธ๋‹ค. ์šฐ์„ ์€ ์ด๋ ‡๊ฒŒ ๋‘๊ณ  ํ›„์— ๋ฆฌํŒฉํ† ๋ง์„ ๊ณ ๋ฏผํ•ด ๋ด์•ผ๊ฒ ๋‹ค.

2. ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์œผ๋ฉด ์œ ์ € ์ •๋ณด๊ฐ€ ์‚ญ์ œ๋˜๋„๋ก ํ•˜๋ ค๋ฉด

firebase api๋ฅผ ์‚ฌ์šฉํ–ˆ๋”๋‹ˆ '๋กœ๊ทธ์•„์›ƒ' api๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š์œผ๋ฉด user ์ •๋ณด๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ์กด์žฌํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. ์ƒํ™ฉ์—๋”ฐ๋ผ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ ๋‚˜๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ซํžˆ๋ฉด ์ž๋™์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ ๋˜๊ณ  user ์ •๋ณด๊ฐ€ ์‚ญ์ œ๋˜๊ธฐ๋ฅผ ๋ฐ”๋žฌ๋‹ค.

firebased์—์„œ ๊ด€๋ จ ์„ค์ •์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” api๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ ์ด ์„ธ๊ฐ€์ง€ ์ง€์† ์œ ํ˜•์ด ์žˆ๋‹ค.
1. LOCAL : '๋กœ๊ทธ์•„์›ƒ'์„ ํ•˜์ง€ ์•Š๋Š” ์ด์ƒ ๊ณ„์† ์ง€์†.
2. SESSION : ์ธ์ฆ ์ƒํƒœ๊ฐ€ ์„ธ์…˜์— ํ•œํ•ด์„œ ์ง€์†๋˜๊ณ  ํƒญ์ด๋‚˜ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ซํžˆ๋ฉด ์‚ญ์ œ๋จ.
3. NONE: ์ƒํƒœ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์ €์žฅ๋˜๊ณ  ์ƒˆ๋กœ ๊ณ ์นจํ•˜๋ฉด ์‚ญ์ œ๋จ.

๋‚˜๋Š” ์„ธ์…˜ ๋ฐฉ์‹์„ ์„ ํƒํ•ด์„œ ์›ํ•˜๋Š”๋Œ€๋กœ ๋™์ž‘์‹œํ‚ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

profile
๋” ์ด์ƒ ๋ฏธ๋ฃฐ ์ˆ˜ ์—†๋‹ค

0๊ฐœ์˜ ๋Œ“๊ธ€