* (수정한 내용)

  • 로그인 후 다른 페이지로 이동 시 기존 redux 부분이 사라짐
    ▶ a태그 사용 대신 button에 onClick을 해당 함수와 연결

* 프로젝트 구조

[components]
└ LoginForm.js (로그인 화면)
└ SuccessLogin.js (로그인 완료 화면)

[pages]
└ _app.js (redux 연결)
└ signin.js (로그인 컴포넌트가 보여지는 곳)

[redux]
└ store.js
└ feaure
└ userSlice.js
└ sagas
└ rootSaga.js
└ userSaga.js

* 결과 화면

로그인 화면



* 만나게 된 오류들

  1. 리덕스 사가와 리덕스 툴킷 연결에 어려움 겪음
    (참고한 강의(omdb로 영화 사이트 만들기(검색 포함)) : https://www.youtube.com/watch?v=DPOzlL1fpnU)
    (참고한 블로그(트렌비 기술블로그, 리덕스 사가 자세히 기술)) : https://tech.trenbe.com/2022/05/25/Redux-Saga.html)
    ▶ 리덕스 사가에서 takeLatest에서 type 추가
    takeLatest : 액션(Action)이 디스패치(dispatch)될 때 이전에 실행 중인 작업(Task)이 있다면 취소하고 새로운 작업을 분기(fork)함.

  2. Redux-devtools로 확인 시 로그인 완료(SuccessLogin.js)가 loginSuccess에서 나와야 하는데 loginRequest에서 나옴(또한 로그인 후 리로딩 되고 다시 원래 로그인 화면이 나옴(원래 의도 : 로그인 화면(LoginForm.js) 후 로그인 완료 시 로그인 완료(SuccessLogin.js)가 나오길 원함
    ▶ 리덕스 기능 중 useSelector에서 다른 항목으로 가져옴(me)를 가져와야 했음

  3. 로그인, 회원가입 둘 다 리로딩 발생 : pages에서 바로 화면요소가 나타나는 것이 아니었기 때문(components를 불러옴, 게다가 성공 여부를 화면에 다시 띄워주길 원했으므로 삼항 연산자로 useSelector에서 가져온 데이터로 구분함)

* 코드(로그인 - 리덕스)

1. feature/userSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  loginLoading: false, //로그인 시도
  loginComplete: false,
  loginError: null,
  logoutLoading: false, //로그아웃 시도
  logoutComplete: false,
  logoutError: null,
  me: null,
  signUpData: {},
  loginData: {},
};

export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    //로그인
    loginRequest: (state) => {
      state.loginLoading = true;
      state.loginError = null;
      state.loginComplete = false;
    },
    loginSuccess: (state, action) => {
      state.loginLoading = false;
      state.loginComplete = true;
      state.me = action.payload; //state.me = testUser(action.data);
      console.log("state.me", state.me);
    },
    loginFailure: (state, action) => {
      state.loginLoading = false;
      state.loginError = action.error;
    },
    //로그아웃
    logoutRequest: (state) => {
      state.logoutLoading = true;
      state.logoutError = null;
      state.logoutComplete = false;
    },
    logoutSuccess: (state, action) => {
      state.me = action.payload;

      state.logoutLoading = false;
      state.logoutComplete = true;
    },
    logoutFailure: (state, action) => {
      state.logoutLoading = false;
      state.logoutError = action.error;
    },
  }
});

export const {
  loginRequest,
  loginSuccess,
  loginFailure,
  logoutRequest,
  logoutSuccess,
  logoutFailure,
} = userSlice.actions;

export default userSlice.reducer;

2. sagas/userSaga.js

import { takeLatest, put, fork, call, delay } from "redux-saga/effects";
import {
  loginRequest,
  loginSuccess,
  loginFailure,
  logoutRequest,
  logoutSuccess,
  logoutFailure,
} from "../feature/userSlice";

function* logIn(action) {
  try {
    const data = action.payload;
    console.log("data", data);
    yield put(loginSuccess({ data: data }));
    // yield call(loginSuccess, data);
  } catch (error) {
    yield put(loginFailure(error));
    console.log(error);
  }
}

function* logOut() {
  try {
    // yield delay(1000);
    yield put(logoutSuccess());
  } catch (error) {
    yield put(logoutFailure(error));
    console.log(error);
  }
}

function* login_Req() {
  yield takeLatest(loginRequest.type, logIn);
}

function* logout_Req() {
  yield takeLatest(logoutRequest.type, logOut);
}

export const userSagas = [fork(login_Req), fork(logout_Req)];

3. sagas/rootSaga.js

▶ 여러 개의 saga 연결

import { all } from "redux-saga/effects";
import { userSagas } from "./userSaga";

export default function* rootSaga() {
  yield all([...userSagas]);
}

4. store.js

▶ redux-toolkit과 redux-saga 연결

import { configureStore } from "@reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import UserReducer from "./feature/userSlice";
import rootSaga from "./sagas/rootSaga";

const sagaMiddleware = createSagaMiddleware();

const store = configureStore({
  reducer: {
    user: UserReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(sagaMiddleware),
});

sagaMiddleware.run(rootSaga);

export default store;

* 코드(로그인 - pages)

1. pages/_app.js

▶ store을 Provider에 연결

import React from "react";
import Head from "next/head";
import "tailwindcss/tailwind.css";
import { Provider } from "react-redux";
import store from "../redux/store";

const EngWordSNS = ({ Component }) => {
  return (
    <>
      <Provider store={store}> 
        <Head>
          <title>engWord</title>
        </Head>
        <Component />
      </Provider>
    </>
  );
};

export default EngWordSNS;

2. pages/signin.js

▶ useSelector(스토어의 상태값을 반환해주는 역할)로 state.user의 me를 가져옴
▶ userSlice.js에서 me의 초기값은 null이므로, 로그인 전에sms LoginForm만 보여주고 로그인 이후 me에 값이 들어가면 SuccessLogin이 보여줌(삼항연산자 사용)

import React from "react";
import LoginForm from "../components/LoginForm";
import SuccessLogin from "../components/SuccessLogin";
import NavbarForm from "../components/NavbarForm";

import { useSelector } from "react-redux";

const signIn = () => {
  const { me } = useSelector((state) => state.user);
  console.log("me", me);
  return (
    <div>
      <NavbarForm />
      {me ? <SuccessLogin /> : <LoginForm />}
    </div>
  );
};

export default signIn;

* 코드(로그인 - components)

1. components/LoginForm.js

▶ dispatch : 액션 값과 상태에 관한 데이터를 리듀서(Reducer) 함수에 전달
▶ dispatch를 통해 loginRequest 불러옴(email, password 들어감)

import React, { useCallback } from "react";
import { LockClosedIcon, BookmarkIcon } from "@heroicons/react/20/solid";
import useInput from "../hooks/useInput";
import { useDispatch } from "react-redux";
import { loginRequest } from "../redux/feature/userSlice";

const LoginForm = () => {
  const dispatch = useDispatch();

  const [email, onChangeEmail] = useInput("");
  const [password, onChangePassword] = useInput("");

  const onSubmitForm = useCallback(() => {
    console.log(email, password);
    dispatch(
      loginRequest({
        email,
        password,
      })
    );
  }, [email, password]);

  return (
    <div className="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
      <div className="w-full max-w-md space-y-8">
        <div>
          <div className="mt-20 mx-auto w-auto bg-light-beige rounded-md h-10 w-10">
            <BookmarkIcon />
          </div>
          <h4 className="mt-8 text-center text-3xl font-bold tracking-tight text-gray-900">
            <span className="text-light-brown">EngWord</span>에 환영합니다!
          </h4>
        </div>
        <form
          onSubmit={onSubmitForm}
          className="mt-8 space-y-6"
          action="#"
          method="POST"
        >
          <input type="hidden" name="remember" defaultValue="true" />
          <div className=" rounded-lg shadow-sm">
            <div>
              <label htmlFor="email-address" className="sr-only">
                Email address
              </label>
              <input
                id="email-address"
                name="email"
                type="email"
                autoComplete="email"
                required
                className="relative mb-2 block w-full appearance-none rounded-lg border-2 border-light-beige px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-dark-green focus:outline-none focus:dark-green sm:text-sm"
                placeholder="Email"
                onChange={onChangeEmail}
              />
            </div>
            <div>
              <label htmlFor="password" className="sr-only">
                Password
              </label>
              <input
                id="password"
                name="password"
                type="password"
                autoComplete="current-password"
                required
                className="relative block w-full appearance-none rounded-lg border-2 border-light-beige px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-dark-green focus:outline-none focus:dark-green sm:text-sm"
                placeholder="Password"
                onChange={onChangePassword}
              />
            </div>
          </div>

          <div className="flex items-center justify-between">
            <div className="flex items-center">
              <a
                href="#"
                className="font-medium text-light-brown hover:text-dark-green font-bold"
              >
                카카오 로그인
              </a>
            </div>

            <div className="text-sm">
              <a
                href="/signup"
                className="font-medium text-dark-green hover:text-light-green font-bold"
              >
                회원가입
              </a>
            </div>
          </div>

          <div>
            <button
              type="submit"
              className="group relative flex w-full justify-center rounded-md border border-transparent bg-dark-green py-2 px-4 text-sm font-medium text-white hover:bg-light-green focus:outline-none focus:ring-2 focus:ring-light-beige focus:ring-offset-2"
            >
              <span className="absolute inset-y-0 left-0 flex items-center pl-3">
                <LockClosedIcon
                  className="h-5 w-5 text-light-beige group-hover:text-light-beige"
                  aria-hidden="true"
                />
              </span>
              Sign in
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default LoginForm;

2. SuccessLogin.js

▶ dispatch를 통해 logoutRequest 불러옴(me의 값을 null로 다시 만듦)

import React, { useCallback } from "react";
import { useDispatch } from "react-redux";
import { logoutRequest } from "../redux/feature/userSlice";
import Router from "next/router";

const SuccessLogin = () => {
  const dispatch = useDispatch();
  const onLogout = useCallback(() => {
    dispatch(logoutRequest());
  }, []);

  const onMain = () => {
    Router.push("/");
  };
  return (
    <>
      <div className=" flex min-h-full items-center justify-center py-48 px-4 sm:px-6 lg:px-6">
        <div className="bg-gray-100 mx-auto max-w-7xl py-12 px-8 sm:px-6 lg:items-center lg:justify-between lg:py-16 lg:px-8">
          <h2 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
            <span className="block text-center mb-2">test님</span>
            <span className="block text-dark-green">
              로그인이 완료되었습니다!
            </span>
          </h2>
          <div className="mt-8 flex ">
            <div className="inline-flex rounded-md shadow">
              <button
                onClick={onMain}
                className="inline-flex items-center justify-center rounded-md border border-transparent bg-light-beige px-5 py-3 text-base font-medium text-black font-bold hover:bg-light-brown hover:text-white"
              >
                메인화면
              </button>
            </div>
            <button
              onClick={onLogout}
              className="ml-3 inline-flex items-center justify-center rounded-md border border-transparent bg-light-orange px-5 py-3 text-base font-medium text-black hover:bg-light-green hover:text-white"
            >
              로그아웃
            </button>
          </div>
        </div>
      </div>
    </>
  );
};

export default SuccessLogin;
profile
개발 블로그, 티스토리(https://ba-gotocode131.tistory.com/)로 갈아탐

0개의 댓글