[components]
└ LoginForm.js (로그인 화면)
└ SuccessLogin.js (로그인 완료 화면)
[pages]
└ _app.js (redux 연결)
└ signin.js (로그인 컴포넌트가 보여지는 곳)
[redux]
└ store.js
└ feaure
└ userSlice.js
└ sagas
└ rootSaga.js
└ userSaga.js
리덕스 사가와 리덕스 툴킷 연결에 어려움 겪음
(참고한 강의(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)함.
Redux-devtools로 확인 시 로그인 완료(SuccessLogin.js)가 loginSuccess에서 나와야 하는데 loginRequest에서 나옴(또한 로그인 후 리로딩 되고 다시 원래 로그인 화면이 나옴(원래 의도 : 로그인 화면(LoginForm.js) 후 로그인 완료 시 로그인 완료(SuccessLogin.js)가 나오길 원함
▶ 리덕스 기능 중 useSelector에서 다른 항목으로 가져옴(me)를 가져와야 했음
로그인, 회원가입 둘 다 리로딩 발생 : pages에서 바로 화면요소가 나타나는 것이 아니었기 때문(components를 불러옴, 게다가 성공 여부를 화면에 다시 띄워주길 원했으므로 삼항 연산자로 useSelector에서 가져온 데이터로 구분함)
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;
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)];
▶ 여러 개의 saga 연결
import { all } from "redux-saga/effects";
import { userSagas } from "./userSaga";
export default function* rootSaga() {
yield all([...userSagas]);
}
▶ 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;
▶ 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;
▶ 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;
▶ 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;
▶ 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;