React

[TIL/React] 2023/08/15 ๐Ÿ‡ฐ๐Ÿ‡ท

LogIn ์ƒํƒœ, ์ „์—ญ์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ(feat. Redux Toolkit) ๐ŸŸ  > src/pages/LogInPage.js ๐Ÿ”ต > src/modules/authSlice.js ๐Ÿ”ต > ํ•ด๊ฒฐ ๊ณผ์ • ๐ŸŸข ๊ฐœ์š” ์–ด๋–ค action์„ ํ–‰ํ•จ์— ์žˆ์–ด, ํŠนํžˆ ๊ฐœ๋ฐœ์ž๋Š” ๋”ฐ์œ„์˜ ๋Œ€๋‹ต์„ ํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค. ์— ๋Œ€ํ•œ ๊ทผ๊ฑฐ๋ถ€ํ„ฐ ์„œ์ˆ ํ•˜๊ณ ์ž ํ•œ๋‹ค. ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ๋Š”, ์†Œ...

2023๋…„ 8์›” 15์ผ3min read

LogIn ์ƒํƒœ, ์ „์—ญ์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ(feat. Redux Toolkit) ๐ŸŸ 

src/pages/LogInPage.js ๐Ÿ”ต

code
import React, { useState, useEffect } from "react";
import styled from "@emotion/styled";
import { loginUser } from "../modules/authSlice";
import { useNavigate } from "react-router-dom";
import ComponentWrapper from "../components/common/ComponentWrapper";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";

const LogInTitle = styled.p`
  text-align: center;
  margin: 0px;
  font-weight: bolder;
  font-size: 40px;
`;

const LogInFormWrapper = styled.div`
  /* display: flex;
  flex-direction: column;
  justify-content: center; */
  /* min-width: 375px;
  width: 100%;
  height: 500px; */
  /* margin: 0 auto; */
`;

const FormLabel = styled.p`
  font-size: 16px;
  margin-bottom: 8px;
`;

const FormInput = styled.input`
  padding: 20px;
  margin-bottom: 16px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font-size: 16px;
  width: 100%;
  max-width: 600px;
`;

const SubmitButton = styled.button`
  background-color: black;
  color: #fff;
  padding: 20px;
  border: none;
  border-radius: 8px;
  font-size: 18px;
  font-weight: bolder;
  cursor: pointer;
  width: 100%;
  &:hover {
    background-color: #fff;
    color: black;
    border: 2px solid black;
  }
`;

const LogInPage = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);

  const goToSignUp = () => {
    navigate("/signup");
  };

  const handleSignIn = (e) => {
    e.preventDefault();
    dispatch(loginUser({ email, password }));
  };

  useEffect(() => {
    if (isAuthenticated) {
      navigate("/");
    }
  }, [isAuthenticated]);

  return (
    <ComponentWrapper
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      {/* ๋ชจ๋ฐ”์ผ 900px ์ดํ•˜ */}

      <form onSubmit={handleSignIn}>
        <LogInTitle>๋กœ๊ทธ์ธ</LogInTitle>
        <LogInFormWrapper>
          <h2>์ด๋ฉ”์ผ ๋กœ๊ทธ์ธ</h2>

          <FormLabel>Email</FormLabel>
          <FormInput
            type="email"
            placeholder="์•„์ด๋””(์ด๋ฉ”์ผ ์ฃผ์†Œ)๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />

          <FormLabel>Password</FormLabel>
          <FormInput
            type="password"
            placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />

          <SubmitButton>๋กœ๊ทธ์ธ</SubmitButton>
          <div style={{ display: "flex", columnGap: "8px" }}>
            <p style={{ fontWeight: "bolder" }}> ์ •์œก๊ฐ์ด ์ฒ˜์Œ์ด์‹ ๊ฐ€์š”?</p>
            <p
              style={{
                color: "red",
                fontWeight: "bolder",
                cursor: "pointer",
              }}
              onClick={goToSignUp}
            >
              ํšŒ์›๊ฐ€์ž…ํ•˜๊ธฐ
            </p>
          </div>
        </LogInFormWrapper>
      </form>
    </ComponentWrapper>
  );
};

export default LogInPage;

src/modules/authSlice.js ๐Ÿ”ต

code
// authSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { auth } from "../firebase";
import { signInWithEmailAndPassword } from "firebase/auth";

// Firebase ์ธ์ฆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
export const loginUser = createAsyncThunk(
  "auth/loginUser",
  async (credentials) => {
    const { email, password } = credentials;
    console.log(credentials);

    try {
      const userCredential = await signInWithEmailAndPassword(
        auth,
        email,
        password
      );
      console.log(userCredential);
      return userCredential.user.email;
    } catch (error) {
      console.log(error);
      throw error;
    }
  }
);

const authSlice = createSlice({
  name: "auth",
  initialState: {
    user: null,
    isAuthenticated: false,
  },
  extraReducers: (builder) => {
    builder.addCase(loginUser.fulfilled, (state, action) => {
      console.log(action.payload);
      state.user = action.payload;
      console.log(state.user);
      state.isAuthenticated = true;
    });
  },
});

export default authSlice.reducer;

ํ•ด๊ฒฐ ๊ณผ์ • ๐ŸŸข

1. ๊ฐœ์š”

์–ด๋–ค action์„ ํ–‰ํ•จ์— ์žˆ์–ด, ํŠนํžˆ ๊ฐœ๋ฐœ์ž๋Š” ``๊ทธ๋ƒฅ...` ๋”ฐ์œ„์˜ ๋Œ€๋‹ต์„ ํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค. `์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ``์— ๋Œ€ํ•œ ๊ทผ๊ฑฐ๋ถ€ํ„ฐ ์„œ์ˆ ํ•˜๊ณ ์ž ํ•œ๋‹ค.

์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ๋Š”, ์†Œํ”„ํŠธ์›จ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋‚˜ ๋ชจ๋“ˆ ๊ฐ„์— ๊ณต์œ ๋˜๋Š” ๋ฐ์ดํ„ฐ์™€ ์ƒํƒœ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋ก  ์ค‘ ํ•˜๋‚˜์ด๋‹ค. ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ ๊ด€๋ฆฌํ•จ์œผ๋กœ์จ ์ฝ”๋“œ์˜ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ๊ณผ ๋””๋ฒ„๊น…์˜ ํšจ์œจ์„ฑ, ์ฆ‰ ``์ƒ์‚ฐ์„ฑ์„ ์ œ๊ณ ``ํ•˜๋Š” ๋ฐ์— ๊ทธ ๋ณธ์งˆ์ด ์žˆ๋‹ค.

2. ์ฝ”๋“œ ์ˆ˜์ • ๋ฐ ์—๋Ÿฌ

์œ„ ์ฝ”๋“œ๋Š” ๋กœ๊ทธ์ธ์˜ form ํƒœ๊ทธ์— ์—ฐ๋™๋˜์–ด ์žˆ๋Š” ํ•จ์ˆ˜์ด๋‹ค. ํ•ต์‹ฌ์€ dispatch์ด๋‹ค. form ํƒœ๊ทธ ๋‚ด๋ถ€์—์„œ ์ œ๊ณตํ•˜๋Š” email๊ณผ password๋ฅผ reducer๋กœ ์ „๋‹ฌํ•˜๋Š” ์ •๋„์˜, ์ตœ์†Œํ•œ์˜ ์—ญํ• ๋งŒ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ๋‹ค. ์ „์—ญ ์ƒํƒœ์˜ ๋ณธ์งˆ์— ๋งž๊ฒŒ ๋ฐ์ดํ„ฐ์™€ ์ƒํƒœ์— ๋Œ€ํ•œ ๊ฐ€๊ณต์€ ํ•ด๋‹น ํ•จ์ˆ˜์—์„œ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋‹ค. dispatch๋ฅผ ํ†ตํ•ด ์ „๋‹ฌํ•œ email๊ณผ password๋Š” ์œ„ ํ•จ์ˆ˜๊ฐ€ ๋ฐ›์„ ๊ฒƒ์ด๋‹ค.

์ฒ˜์Œ์—๋Š” ๊ตฌ๊ธ€๋ง์„ ํ†ตํ•ด ``auth.signInWith~~`` ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ, ์œ„ ์—๋Ÿฌ๊ฐ€ console์— ์ฐํ˜”๋‹ค. firebase V9์—์„œ ํ•จ์ˆ˜ ์‚ฌ์šฉ ๋ฐฉ์‹์ด ๋‹ฌ๋ผ์กŒ๊ธฐ์— ๋ฐœ์ƒํ•œ ์—๋Ÿฌ์˜€๋‹ค. ์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ auth๋ฅผ sign ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ–ˆ๋‹ค.

reference: https://velog.io/@seondal/Firebase-v9๋ถ€ํ„ฐ-๋‹ฌ๋ผ์ง„-์ธ์ฆ๋ชจ๋“ˆ-์‚ฌ์šฉ๋ฒ•#2-auth-๊ด€๋ จ-ํ•จ์ˆ˜๋“ค-์‚ฌ์šฉ

๋‹ค์Œ ์—๋Ÿฌ๋Š” try ๋ฌธ ๋‚ด๋ถ€์˜ return ๋ถ€๋ถ„์—์„œ ๋ฐœ์ƒํ–ˆ๋‹ค. ์ง๋ ฌํ™”์— ๊ด€ํ•œ ๋‚ด์šฉ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ๋‹ค. ์ง๋ ฌํ™” ์ž์ฒด์— ๋Œ€ํ•ด ๋‹ค๋ฃจ๋ฉด ์ฝ”๋”ฉ์„ ์—ฌ๊ธฐ์„œ ๋ฉˆ์ถœ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. ๊ฐ„๋‹จํžˆ ์ •๋ฆฌํ•˜๋ฉด, redux์˜ action์—๋Š” ๋น„์ง๋ ฌํ™”๋œ ๊ฐ’์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. firebase์˜ ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋Š” ๋ณต์žกํ•œ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ์— ์ง๋ ฌํ™”ํ•˜๊ธฐ ์–ด๋ ต๋‹ค๊ณ  ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ๋กœ์„œ๋Š” ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋ฅผ ํ†ต์œผ๋กœ returnํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ์‹์„ ํƒํ–ˆ๊ณ , ์—๋Ÿฌ๋Š” ์‚ฌ๋ผ์กŒ๋‹ค.

๊ฒฐ๋ก ์ ์œผ๋กœ fulfilled ๋˜์—ˆ์„ ๋•Œ ์ดˆ๊ธฐ๊ฐ’ user๋Š” ์‚ฌ์šฉ์ž์˜ email๋กœ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค.

๋‹ค์Œ์€ ์˜ค๋Š˜ ๋งˆ์ฃผํ•œ ๋งˆ์ง€๋ง‰ ์—๋Ÿฌ๋‹ค.

ํŽ˜์ด์ง€ ์ด๋™์— ๋Œ€ํ•œ ๋กœ์ง์„ useEffect ํ•จ์ˆ˜ ์™ธ๋ถ€์—์„œ ์ž‘์„ฑํ•œ ํƒ“์— ๋ฐœ์ƒํ•œ ์—๋Ÿฌ์˜€๋‹ค.

isAuthentication์„ ํ†ตํ•ด useEffect ๋‚ด๋ถ€์—์„œ ํŽ˜์ด์ง€ ์ด๋™์„ ์ฒ˜๋ฆฌํ–ˆ๋‹ค. ์ฆ‰, ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด home์œผ๋กœ ์ด๋™ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๋™์ด ์•ˆ ๋๋‹ค.

์˜์กด์„ฑ ๋ฐฐ์—ด์— ํŠน์ • value๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•œ๋‹ค. value๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋ฉฐ, ์ด๋•Œ value๋Š” ๋ Œ๋”๋ง๊ณผ ๊ด€๋ จ๋œ ๊ฐ’์ด์–ด์•ผ ํ•œ๋‹ค.

ํ›„... ๋Œ€ํ•œ๋ฏผ๊ตญ ๋งŒ์„ธ

More to read

REST API

ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ ์‚ฌ์ด

HTTP ์ƒํƒœ ์ฝ”๋“œ๋Š” ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋ƒˆ๋˜ ์š”์ฒญ์˜ ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์˜๋ฏธํ•˜๋Š” ์ผ์ข…์˜ ์•ฝ์†์ด๋ฉฐ, API๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ํ•ต์‹ฌ ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ƒํƒœ ์ฝ”๋“œ์™€ ๊ด€๋ จํ•˜์—ฌ, ๋ฐฑ์—”๋“œ๋Š” ์ž˜ ๋ชจ๋ฅด๋Š” ํ”„๋ก ํŠธ์—”๋“œ์˜ ์Šฌํ”ˆ ์‚ฌ์ •์ด ์žˆ์Šต๋‹ˆ๋‹ค.์•„๋ž˜๋Š” ์š”์ฒญ์ด ์‹คํŒจํ–ˆ์Œ์—๋„, ๋ฐฑ์—”๋“œ์—์„œ ์ƒํƒœ ์ฝ”๋“œ

JWT

ํ† ํฐ ๊ด€๋ฆฌ ๋ฐฉ์‹ ํ†บ์•„๋ณด๊ธฐ

0. ๋“ค์–ด๊ฐ€๋ฉฐ ๐ŸŽฏ ์„œ๋น„์Šค์— ์ ‘๊ทผํ•˜๋ ค๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์„ ์‚ฌ์šฉ์ž ์ธ์ฆ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฃผ์–ด์ง„ ๊ถŒํ•œ์„ ํ™•์ธํ•˜๋Š” ์ž‘์—…์€ ์ธ๊ฐ€๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ธ๊ฐ€๋Š” ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ธ์ฆ์—๋Š” ๋งŽ์€ ๋ฐฉ์‹์ด ์žˆ์ง€๋งŒ, ์˜ค๋Š˜์€ ์„ธ์…˜ ์ธ์ฆ ๋ฐฉ

A2A

A2A / MCP ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜

0. ๋“ค์–ด๊ฐ€๋ฉฐ โœ๏ธ Google for Developers์—, ๋ ˆ์Šคํ† ๋ž‘ ๊ณต๊ธ‰๋ง ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์—ฎ์€ 6๋Œ€ ํ”„๋กœํ† ์ฝœ(MCP, A2A, UCP, AP2, A2UI, AG-UI)์— ๋Œ€ํ•œ ๊ฐ€์ด๋“œ๊ฐ€ ๊ฒŒ์‹œ๋œ ์ดํ›„, MCP์™€ A2A๋ถ€ํ„ฐ ๊ตฌํ˜„ํ•ด ๋ณด๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ