
오늘 만든 것
최근 노마드코더의 트위터 클론 강의를 듣고 있습니다. CRUD 맛을 보고 싶어서 트위터 클론 강의를 선택했습니다.
아직 초반 단계이지만 역시 버전 이슈는 항상 등장하네요. react 관련 (ex : react-router-dom) 버전은 강의에 맞추어주었습니다.
firebase의 경우 docs를 확인하면서 제 버전에 맞게 작성하고 있습니다. 그 결과 현재까지는 문제가 없습니다!
폴더 구조
Authentcation
- create account
- log in
- log out
로그인했을 때 안 했을 때 보여줄 페이지
import React from "react";
import {HashRouter as Router,Redirect,Route,Switch,} from "react-router-dom";
import Auth from "routes/Auth";
import Home from "routes/Home";
import Navigation from "components/Navigation";
import Profile from "routes/Profile";
const AppRouter = (props) => {
return (
<Router>
{props.isLooggedIn && <Navigation />}
<Switch>
{props.isLooggedIn ? (
<>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/profile">
<Profile />
</Route>
</>
) : (
<>
<Route exact path="/">
<Auth />
</Route>
</>
)}
</Switch>
</Router>
);
};
react-routeter-dom v5 기준이기 때문에 알고 있으신 내용과 다를 수 있습니다.
HashRouter는 주소에 #를 붙여줍니다. 해시를 사용하면 서버에 요청을 하지 않아서 새로고침해도 에러가 발생하지 않는다고 하네요.
그리고 배포가 간편합니다. 간단한 프로젝트이기 때문에 HashRouter를 사용한 것으로 보입니다.
Log In
AppRouter에서는 props로 사용자의 로그인 정보를 가지고 와서 로그인했을 경우 Nav 바를 렌더링 해줍니다.
그리고 '/' 경로인 Home 컴포넌트가 렌더링 됩니다.
이때 exact를 사용해야 합니다. exact는 경로가 완벽히 일치하는 컴포넌트만 렌더링 합니다.
만약에 exact가 없다면 url이 ~/#/'profile'일 때 '/profile'과 '/'에서 '/' 부분이 겹칩니다.
따라서 '/profile'이 포함된 url에서 아래와 같이 Home 컴포넌트도 렌더링 되는 상황이 나타납니다.
이를 해결하기 위해서는 exact를 사용해줘야 합니다.
Log Out
로그아웃 상태에서는 로그인을 위한 페이지인 Auth 컴포넌트를 렌더링 해줍니다.
Navigation bar
import React from "react";
import { Link } from "react-router-dom";
function Navigation() {
return (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/profile">My Profile</Link>
</li>
</ul>
</div>
);
}
export default Navigation;
아주 심플합니다 My Profile을 누르면 '/profile'로 Home을 누르면 '/'로 경로를 변경해 줍니다.
App.js
import { useState, useEffect } from "react";
import AppRouter from "components/Router";
import { authService } from "fbInstnace";
function App() {
const [init, setInit] = useState(false);
const [isLooggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
authService.onAuthStateChanged((user) => {
if (user) {
setIsLoggedIn(true);
} else setIsLoggedIn(false);
setInit(true);
});
}, []);
return (
<>{init ? <AppRouter isLooggedIn={isLooggedIn} /> : "Initializing..."}</>
);
}
export default App;
isLoggedIn은 사용자가 로그인했는지 여부를 저장하는 state입니다.
사용자의 로그인 여부는 onAuthStateChanged()를 통해 감지할 수 있습니다.
firebase docs에서 자세하게 확인하실 수 있겠지만 firebase에서는 저 친구를 observer for changes to the user's sign-in state. 라고 합니다. 즉 유저 상태에 변화가 있을 때 그 변화를 알아차리는 이벤트 리스너라는 겁니다.
사용자가 로그인을 하거나 로그아웃을 했을 때 트리거됩니다. 로그아웃 시에 user 값은 null입니다.
콜백을 필요로 합니다. user 값이 truth라면 로그인이 되었다는 뜻이기 때문에 isLoggedIn 값을 true로 갱신합니다. 로그아웃 시에는 false로 만들어주면 되겠죠? 그리고 setInit 값을 true로 바꿔줍니다.
onAuthStateChanged가 실행됐다는 건 로그인 여부를 가지고 올 수 있다는 뜻이기도 합니다.
Init을 true로 갱신하고 로그인 여부를 AppRouter 컴포넌트에 전달하면 되는 겁니다.
여기서 Init은 firebase가 프로그램을 초기화하는 걸 기다려주는 state입니다. 초기값은 당연히 false고요. 초기화가 됐다면 라우터를 보여주고 아니라면 해당하는 문구를 보여줍니다. 초기화가 됐을 경우 AppRouter 컴포넌트에 로그인여부(isLoggedIn)도 전달해 줍니다. 그 이후는 위에서 설명한 내용이 동작하겠죠.
Auth.js
import { authService } from "fbInstnace";
import {
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
GoogleAuthProvider,
GithubAuthProvider,
signInWithPopup,
} from "firebase/auth";
import React, { useState } from "react";
function Auth() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [newAccount, setNewAccont] = useState(true);
const [error, setError] = useState("");
const onChange = (event) => {
const {
target: { name, value },
} = event;
if (name === "email") setEmail(value);
else if (name === "password") setPassword(value);
};
const onSubmit = async (event) => {
event.preventDefault();
try {
let data;
if (newAccount) {
data = await createUserWithEmailAndPassword(
authService,
email,
password
);
//create
} else {
//login
data = await signInWithEmailAndPassword(authService, email, password);
}
console.log(data);
} catch (error) {
setError(error.message);
}
};
const toggleAccount = () => {
setNewAccont(!newAccount);
};
const onSocialClick = async (event) => {
const {
target: { name },
} = event;
let provider;
if (name === "google") {
provider = new GoogleAuthProvider();
} else if (name === "github") {
provider = new GithubAuthProvider();
}
const data = await signInWithPopup(authService, provider);
console.log(data);
};
return (
<div>
<form onSubmit={onSubmit}>
<input
name="email"
type="email"
placeholder="Email"
required
value={email}
onChange={onChange}
/>
<input
name="password"
type="password"
placeholder="Password"
required
value={password}
onChange={onChange}
/>
<input type="submit" value={newAccount ? "Create Account" : "Log In"} />
{error}
</form>
<span onClick={toggleAccount}>
{newAccount ? "Sign in" : "Create Account"}
</span>
<div>
<button name="google" onClick={onSocialClick}>
Continue with Google
</button>
<button name="github" onClick={onSocialClick}>
Continue with Github
</button>
</div>
</div>
);
}
export default Auth;
함수부터 하나씩 살펴봅시다.
onChange
Email과 password를 입력하는 input 요소에 변화가 있을 때 email과 password state 값을 갱신해주는 함수입니다.
submit 버튼 클릭시 실행되는 함수입니다. submit버튼은 newAccount 값에 따라 콘텐츠가 바뀝니다.
true일 때 계정을 생성할 수 있습니다. false일 때는 로그인을 합니다.
createUserWithEmailAndPassword를 사용하여 신규 계정을 생성할 수 있습니다. 인자로는 auth, email, password를 받습니다.
signInWithEmailAndPassword로 로그인을 처리할 수 있습니다. 마찬가지로 auth, email, password를 인자로 받습니다.
계정 생성에 성공할 경우 다음과 같은 data를 받습니다. firebase Authentication에 User가 추가된 것도 확인할 수 있습니다.
참고로 사용자 계정이 성공적으로 생성되면 해당 사용자는 바로 로그인이 됩니다.
이미 가입한 email이거나 비밀번호 조건에 부합하지 않을 경우 error가 발생하고 사용자 계정 생성에 실패합니다.
toggleAccount
newAccount 값을 toggle 하는 함수입니다. span을 클릭 시 newAccount 값이 true->false, false> true로 바뀝니다.
onSocialClick
구글과 깃허브 계정을 통해 로그인을 할 수 있습니다.
구글만 보도록 하죠.
GoogleAuthProvider
구글 계정으로 인증 처리를 하기 위해 사용합니다.
GoogleAuthProvider 클래스의 인스턴스를 생성합니다. 이 인스턴스를 SignInWithPop() 함수의 인자로 사용해 사용자가 구글 계정으로 로그인할 수 있게 할 수 있습니다.
SignInWithPopup() 인자로 auth, provider, resolver(optional)를 받습니다.
팝업 기반으로 Firebase 클라이언트를 인증합니다. 성공 시 로그인한 사용자와 공급자의 자격 증명을 반환해 주고 실패 시 오류에 대한 개체를 반환합니다.
Profile.js
import { authService } from "fbInstnace";
import React from "react";
import { useHistory } from "react-router-dom";
function Profile() {
const history = useHistory();
const onLogOutClick = () => {
authService.signOut();
history.push("/");
};
return (
<>
<button onClick={onLogOutClick}>Log Out</button>
</>
);
}
export default Profile;
nav에서 profile을 클릭해 profile 컴포넌트를 렌더링 할 수 있는데요. 내용은 간단합니다.
버튼 클릭 시 실행되는 함수에서 signOut을 통해 로그아웃을 합니다. signOut도 firebase Authentication sdk에서 제공하는 함수입니다. 로그인된 사용자를 로그아웃 시켜줍니다.
로그아웃 후에 '/' 즉 Auth 뷰를 렌더링 해줍니다.
이제 동작을 살펴볼까요
기억할 것
firebase docs를 끼고 살펴보면서 구현합시다. 강의만 따라가기엔 바뀐 내용이 너무 많습니다.
시간 미쳤다..
맞다.. 무비앱 클론은 버전이슈로 일단 미국보냈습니다..
'clone' 카테고리의 다른 글
ntwitter 클론(2) (0) | 2023.03.30 |
---|---|
무비앱 인프런 강의 클론(3) (1) | 2023.03.18 |
무비앱 인프런 강의 클론(2) (0) | 2023.03.17 |
무비앱 인프런 강의 클론(1) (1) | 2023.03.16 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!