일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- SCSS extend
- vuex map
- nextjs사용법
- postcss
- vue mixin
- flex
- 리액트
- SCSS use
- SCSS import
- 프로그래머스 K_Digital Training
- vue 이벤트 수신
- git 같은계정 다른 컴퓨터
- 쌓임맥락
- react next
- 폼 입력 바인딩
- 프로그래머스 프론트엔드 데브코스
- 프로그래머스 데브코스
- intersection opserver
- SCSS forward
- netlify redirect
- 이벤트 수식어
- Vue
- 다른컴퓨터에서 git사용
- KDT 프로그래머스 데브코스 프론트엔드
- vue 지역 컴포넌트
- 고양이 사진 검색기
- KDT 프로그래머스
- Spacer
- 리스트 렌더링
- 프로그래머스 데브코스 프론트엔드
- Today
- Total
혼자 적어보는 노트
redux를 사용한 firebase 이메일 로그인 / react-redux-firebase 본문
✨ redux를 이용하여 firebase 이메일 로그인 구현해보기 ✨
이전에 firebase v9와 contextAPI를 활용하여 로그인을 구현했었지만
혼자 해보고 있는 프로젝트에 redux를 사용하다보니 redux로 연결을 해보고 싶었다.
여기저기 찾아보았는데 하는 방법이 많이 있지는 않고
특히나 firebase v9와 활용하는 사례는 찾을 수 없었다..
어찌저찌 유튜브 영상을 찾게되어 해보면서 정리를 하게 되었다.
일단 v8으로 만들어보고 익숙해지면 v9를 문서와 함께 보면서 활용해 볼 예정이다.
✅ 적용해볼 것들
1. name, email, password를 이용한 회원가입
2. email, password 로그인
3. 새로고침 후에도 로그인 상태 유지
4. 로그인 상태에 따른 ui 표시
사용한 버전은 firebase 8.9 이다
참고한 동영상 : https://www.youtube.com/watch?v=vtvVZL_nSwc&t=4469s
📂package 설치
yarn add redux react-redux redux-thunk redux-logger firebase
기본적으로 redux, react-redux를 설치해 주었고
미들웨어인 redux-thunk와 redux-logger, firebase까지 설치 했다.
react-router-dom@5도 설치해주었다.
(최신버전으로 작업하기에는 변수가 있을 것 같아서 일단 v5로 진행했다.)
🔥firebase 세팅
firebase 프로젝트 생성 및 앱 생성 후
이메일 비밀번호로 로그인설정을 해준다.
[firebase.js]
import firebase from "firebase/app";
import "firebase/auth";
const firebaseConfig = {
apiKey: process.env.REACT_APP_API_KEY,
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
projectId: process.env.REACT_APP_PROJECT_ID,
storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_MESSAGING_ID,
appId: process.env.REACT_APP_APP_ID,
};
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
const githubAuthProvider = new firebase.auth.GithubAuthProvider();
export { auth, googleAuthProvider, githubAuthProvider };
.env에 각 코드들을 담아주고
이후에 google로그인과 github로그인을 추가할 것을 고려하여 세팅을 해준다.
📂 component 만들기
Home, Login, Register 총 세개의 컴포넌트를 만든다.
[Home]
const Home = (props) => {
const handleAuth = () => {};
return (
<div className="home">
<h1>Home</h1>
<button onClick={handleAuth}>Logout</button>
<button>
<Link to="/login">Login</Link>
</button>
)}
</div>
);
};
Home.js에는 로그인의 상태에 따라 Login과 Logout의 버튼이 노출 될 수 있게 만들어준다.
[Login.js]
const Login = (props) => {
const [state, setState] = useState({
email: "",
password: "",
});
const handleSubmit = (e) => {};
const handleChange = (e) => {
let { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
<div className="login">
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
id="inputEmail"
plaseholder="Email Address"
onChange={handleChange}
value={email}
required
/>
<input
type="password"
name="password"
id="inputPassword"
plaseholder="Password Address"
onChange={handleChange}
value={password}
required
/>
<button type="submit">Login</button>
<Link to="/register">Sign up</Link>
</form>
</div>
);
};
Login에는 email과 password를 입력하고 전송하는 form과 Sign up버튼을 만들어준다.
전에는 state값을 따로 만들었었는데
name과 value이용해서 한 객체에 넣음으로써 한결 깔끔하게 담는 방법을 알게되었다.
[Register.js]
const Register = (props) => {
const [state, setState] = useState({
displayName: "",
email: "",
password: "",
passwordConfirm: "",
});
const { displayName, email, password, passwordConfirm } = state;
const handleSubmit = (e) => {
e.preventDefault();
if (password !== passwordConfirm) {
alert("password가 일치하지 않습니다.");
return;
}
};
const handleChange = (e) => {
let { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
<div className="login">
<h1>Join</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
name="displayName"
id="inputEmail"
plaseholder="displayName"
onChange={handleChange}
value={displayName}
required
/>
<input
type="email"
name="email"
id="inputEmail"
plaseholder="Email Address"
onChange={handleChange}
value={email}
required
/>
<input
type="password"
name="password"
id="inputPassword"
plaseholder="Password Address"
onChange={handleChange}
value={password}
required
/>
<input
type="password"
name="passwordConfirm"
id="inputPassword"
plaseholder="Repeat Password"
onChange={handleChange}
value={passwordConfirm}
required
/>
<button type="submit">Sign Up</button>
<Link to="/login">Login</Link>
</form>
</div>
);
};
Register.js에는 name, email, password, passwordConfirm의 값을 전송하는 form을 만들어주고
Login페이지로 이동 되는 button을 만들어주었다.
🎫 Router 만들기
import "./App.css";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "./components/Home";
import Login from "./components/Login";
import Register from "./components/Register";
function App() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
📁 redux store 만들기
개인적으로 action, actiontype, reducer을 한 파일에서 관리하는게 편해서
module폴더 안에 index.js와 reducer.js 파일만 만들어 주었다.
logger와 thunk는 처음 써봤는데 redux-logger를 사용하면 콘솔에서
state의 변경 내역이 나타난다.👍
redux-thunk를 사용하여 API호출을 할 수 있다.
이번에 살짝 맛보기?를 해보았지만 좀 더 사용해본 후 관련 정리를 한번 해 볼 예정이다.
[index.js]
import { combineReducers, createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import userReducer from "./reducer";
import thunk from "redux-thunk";
const rootReducer = combineReducers({
user: userReducer,
});
const middleware = [thunk];
if (process.env.NODE_ENV === "development") {
middleware.push(logger);
}
export const store = createStore(rootReducer, applyMiddleware(...middleware));
process.env.NODE_ENV가 develepment일 때만 logger를 추가해 주었는데
항상 logger를 띄우려면 const middleware = [thunk, logger];를 해주어도 된다.
middlewere가 2개 이상이라면 logger은 맨 뒤에 위치해야 한다.
🔨 reducer 세팅
import { auth } from "../firebase";
import firebase from "firebase/app";
// Action-types
const REGISTER_START = "REGISTER_START";
const REGISTER_SUCCESS = "REGISTER_SUCCESS";
const REGISTER_FAIL = "REGISTER_FAIL";
// Actions
const registerStart = () => ({
type: REGISTER_START,
});
const registerSuccess = (user) => ({
type: REGISTER_SUCCESS,
payload: user,
});
const registerFail = (error) => ({
type: REGISTER_FAIL,
payload: error,
});
const initialState = {
loading: false,
currentUser: null,
error: null,
};
export const registerInitiate = (email, password, displayName) => {
};
// Reducer
const userReducer = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
회원가입에 관한 action type과 actions, reducer의 초기 세팅을 적어준다.
registerInitiate에서 데이터들을 받은 후에
api 호출을 한 후에 결과에 맞게 dispatch를 할 예정이다.
📑 Email, Password 가입
Register 컴포넌트에서 실행될 기능은 아래와 같다.
1. 사용자에게 입력받은 displayName, email, password를 dispatch로 전달
2. 로그인된 상태에서 Register 컴포넌트 접근 시 Home으로 redirect.
[Register.js]
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useHistory, Link } from "react-router-dom";
import { registerInitiate } from "../modules/reducer";
const Register = (props) => {
const [state, setState] = useState({
displayName: "",
email: "",
password: "",
passwordConfirm: "",
});
const { displayName, email, password, passwordConfirm } = state;
const { currentUser } = useSelector((state) => state.user);
const dispatch = useDispatch();
const history = useHistory();
useEffect(() => {
if (currentUser) {
console.log(currentUser);
history.push("/");
}
}, [currentUser]);
const handleSubmit = (e) => {
e.preventDefault();
if (password !== passwordConfirm) {
alert("password가 일치하지 않습니다.");
return;
}
dispatch(registerInitiate(email, password, displayName));
setState({ email: "", displayName: "", password: "", passwordConfirm: "" });
};
const handleChange = (e) => {
let { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
// ...생략
);
};
[reducer.js]
export const registerInitiate = (email, password, displayName) => {
return function (dispatch) {
dispatch(registerStart);
auth
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
user.updateProfile({
displayName,
});
console.log(user);
dispatch(registerSuccess(user));
})
.catch((error) => {
console.log(error.message);
dispatch(registerFail(error.message));
});
};
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case REGISTER_START:
return {
...state,
loading: true,
};
case REGISTER_SUCCESS:
return {
...state,
loding: false,
currentUser: action.payload,
};
case REGISTER_FAIL:
return {
...state,
loding: false,
error: action.payload,
};
default:
return state;
}
};
createUserWithEmailAndPassword로 email,password를 전송하여 계정을 생성 한 후에
user데이터에 updateProfile으로 name을 추가해주는 방식이다.
dispatch호출과 동시에 loading을 true로 변경했는데
호출 시 ui로 로딩 처리를 하고 싶다면 해당 상태를 참조하면 될 것이다.
정상적으로 api호출에 성공했다면 registerSuccess를, 실패했다면 registerFail을 호출한다.
이번에도 firebase console의 authentication에서 계정을 테스트하면서 진행 했다.
📑 Email, Password 로그인
Login 컴포넌트에서 구현할 기능은 Register과 비슷하다.
1. 사용자에게 입력받은 email, password를 dispatch로 전달
2. 로그인된 상태에서 Login 컴포넌트 접근 시 Home으로 redirect.
[Login.js]
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useHistory, Link } from "react-router-dom";
import { loginInitiate } from "../modules/reducer";
const Login = (props) => {
const [state, setState] = useState({
email: "",
password: "",
});
const { email, password } = state;
const { currentUser } = useSelector((state) => state.user);
const dispatch = useDispatch();
const history = useHistory();
useEffect(() => {
if (currentUser) {
console.log(currentUser);
history.push("/");
}
}, [currentUser]);
const handleSubmit = (e) => {
e.preventDefault();
if (!email || !password) {
return;
}
dispatch(loginInitiate(email, password));
setState({ email: "", password: "" });
};
const handleChange = (e) => {
let { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
// ... 생략
);
};
[reducer.js]
const loginStart = () => ({
type: LOGIN_START,
});
const loginSuccess = (user) => ({
type: LOGIN_SUCCESS,
payload: user,
});
const loginFail = (error) => ({
type: LOGIN_FAIL,
payload: error,
});
export const loginInitate = (email, password) => {
return function (dispatch) {
dispatch(loginStart);
auth
.signInWithEmailAndPassword(email, password)
.then(({ user }) => {
console.log(user);
dispatch(loginSuccess(user));
})
.catch((error) => {
console.log(error.message);
dispatch(loginFail(error.message));
});
};
};
login actionType과 loginInitate를 만들어 준다.
registerInitate와 같은 형식으로 진행한다.
📑 Login 유지
위 코드들을 마치면 회원가입과 로그인은 되지만
새로고침을 할 경우 stast가 초기화 된다.
상위컴포넌트에서 currentUser의 데이터 변경 시 현재 로그인 상태를 확인하여 변경해주어야 한다.
[App.js]
import "./App.css";
import firebase from "firebase/app";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "./components/Home";
import Login from "./components/Login";
import Register from "./components/Register";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setUser } from "./modules/reducer";
function App() {
const { currentUser } = useSelector((state) => state.user);
const dispatch = useDispatch();
useEffect(() => {
firebase.auth().onAuthStateChanged((authUser) => {
if (authUser) {
console.log("Login Check : ", authUser);
dispatch(setUser(authUser));
} else {
dispatch(setUser(null));
console.log("로그인 정보가 없습니다.");
}
});
}, [currentUser]);
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
[reducer.js]
const SET_USER = "SET_USER";
export const setUser = (user) => ({
type: SET_USER,
payload: user,
});
case SET_USER:
return {
...state,
loding: false,
currentUser: action.payload,
};
currentUser의 값 변경시 firebase에 데이터를 요청하여 해당 user데이터를 state에 담아준다.
📑 로그인 상태에 따라 버튼 변경 / Logout 처리
useSelector으로 currentUser의 값을 확인하여 Login과 Logout버튼을 노출
[Home.js]
import { useDispatch, useSelector } from "react-redux";
import { Link, useHistory } from "react-router-dom";
import { logoutInitiate } from "../modules/reducer";
const Home = (props) => {
const dispatch = useDispatch();
const { currentUser } = useSelector((state) => state.user);
const history = useHistory();
const handleAuth = () => {
dispatch(logoutInitiate());
history.push("/");
};
return (
<div className="home">
<h1>Home</h1>
{currentUser ? (
<button onClick={handleAuth}>Logout</button>
) : (
<button>
<Link to="/login">Login</Link>
</button>
)}
</div>
);
};
export default Home;
[Reducer.js]
const LOGOUT_START = "LOGOUT_START";
const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
const LOGOUT_FAIL = "LOGIN_FAIL";
const logoutStart = () => ({
type: LOGOUT_START,
});
const logoutSuccess = () => ({
type: LOGOUT_SUCCESS,
});
const logoutFail = (error) => ({
type: LOGOUT_FAIL,
payload: error,
});
export const logoutInitiate = (email, password) => {
return function (dispatch) {
dispatch(logoutStart);
auth
.signOut()
.then((response) => {
console.log("Login Success : ", response);
dispatch(logoutSuccess());
})
.catch((error) => {
console.log(error.message);
dispatch(logoutFail(error.message));
});
};
};
loginInitiate와 같이 logoutInitate를 작성해준다.
reducer부분
const userReducer = (state = initialState, action) => {
switch (action.type) {
case REGISTER_START:
case LOGIN_START:
case LOGOUT_START:
return {
...state,
loading: true,
};
case LOGOUT_SUCCESS:
return {
...state,
currentUser: null,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
return {
...state,
loding: false,
currentUser: action.payload,
};
case REGISTER_FAIL:
case LOGIN_FAIL:
case LOGOUT_FAIL:
return {
...state,
loding: false,
error: action.payload,
};
case SET_USER:
return {
...state,
loding: false,
currentUser: action.payload,
};
default:
return state;
}
};
export default userReducer;
같은 코드 처리를 하는 case는 위와 같이 겹쳐서 사용할 수 있다! (이번에 알게 됨)
actiontype에 따른 결과값들이 조금 중복된다고 느꼈지만 나누어 작업하는게 이해하기가 편하기는 했다.
📌 마치며
코드를 복사해서 그냥 붙여넣기보다는 조금이라도 이해하면서 하고 싶어서
검색도 해보고 진행을 하느라 시간이 꽤 걸렸다.
해보고 나니 은근 간단한 것 같기도 하고 무엇보다 redux-thunk를 사용해보고 싶었는데
이번 기회에 써보면서 알게되어 좋았다.
검색했을때 자료가 별로 없어서 firebase 8버전을 사용하였지만
9버전으로 다시 수정할 수 있을 것 같다!!
알아보니 로그인 시 state보다는 보통 localstorige나 sessionStorige에
값을 저장한다고 하는데 이것도 만들어봐야겠다.
그리고 만들어보면서 에러 처리나 추가적인 기능들이 생각났었는데
이 부분은 하던 프로젝트에 적용 해 볼 예정이다.
'기타' 카테고리의 다른 글
[Git] log 다루기 / fomatting / --pretty 확장 옵션 / config (0) | 2022.02.11 |
---|---|
redux를 사용한 firebase Google, Github 로그인 / react-redux-firebase (0) | 2022.02.10 |
[Git] 다른 브랜치에서 생성한 커밋 가져오기 / cherry-pick (0) | 2022.02.08 |
[Git] rebase -i를 이용한 커밋(commit) 삭제/수정/병합/분할 (0) | 2022.02.07 |
[Git] 브랜치 다루기 2 - HEAD (0) | 2022.02.05 |