Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7
08 Dec, 2021
TỪ QUỐC HƯNG
AuthorNối tiếp phần 6 của series, trong phần này, chúng ta sẽ bắt tay vào code Sign Up page,config lại phần router để đáp ứng được vấn đề Authentication kèm theo tạo custom hook để listening for Authentication. Không dài dòng nữa, Chúng ta bắt đầu thôi 😁.

Mục Lục
Nối tiếp phần 6 của series, trong phần này, chúng ta sẽ bắt tay vào code Sign Up page, config lại phần router để đáp ứng được vấn đề Authentication kèm theo tạo custom hook để listening for Authentication. Không dài dòng nữa, Chúng ta bắt đầu thôi 😁.
I. Tạo sign up page.
Thì Sign up page của Netflix thì sẽ trông như thế nào? Thì hiện tại cái flow của việc Sign Up tài khoản mới của Netflix thì nó nhiều step hơn rồi. Chúng ta không nhất thiết phải làm y hệt như nó, do đó chúng ta sẽ code Sign Up page như Sign In page, ta chỉ thêm bớt một số thứ thôi.
Chúng ta làm như sau:
Trong file /pages/signup.jsx
, chúng ta sẽ sử dụng lại những gì mà chúng ta đã built-in là component form và headercontainer.
import React, { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import { FirebaseContext } from "../context/firebase";
import { HeaderContainer, FooterContainer } from "../containers/index";
import { Form } from "../components/index";
import * as ROUTES from "../constants/routes";
export default function SignUp() {
const { firebase } = useContext(FirebaseContext);
const history = useHistory();
const [firstName, setFirstName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorMessage, setErrorMessage] = useState("");
// Check form input elements are valid
const isInvalid = firstName === "" || password === "" || email === "";
const handleSignUp = (event) => {
event.preventDefault();
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user
.updateProfile({
displayName: firstName,
photoURL: Math.floor(Math.random() * 5) + 1
})
.then(() => {
history.push(ROUTES.BROWSE);
});
})
.catch((error) => {
setErrorMessage(error.code);
});
};
return (
<>
<HeaderContainer status="hidden">
<Form>
<Form.Title>Sign Up</Form.Title>
{errorMessage && (
<Form.ErrorMessage>{errorMessage}</Form.ErrorMessage>
)}
<Form.Base onSubmit={handleSignUp} method="POST">
<Form.Input
placeholder="First name"
value={firstName}
onChange={({ target }) =>
setFirstName(target.value)
}
/>
<Form.Input
type="email"
placeholder="Your email"
value={email}
onChange={({ target }) => setEmail(target.value)}
/>
<Form.Input
type="password"
placeholder="Your password"
value={password}
onChange={({ target }) => setPassword(target.value)}
/>
<Form.Button disabled={isInvalid} type="submit">
Sign Up
</Form.Button>
<Form.FlexBox style={{ justifyContent: "flex-end" }}>
<Form.SmallLink to="/">Need help?</Form.SmallLink>
</Form.FlexBox>
</Form.Base>
<Form.Wrapper
style={{
flexDirection: "column",
alignItems: "flex-start"
}}
>
<Form.SocialButton>
<Form.Image src="./images/icons/FB-Logo.png" />
<Form.SmallLink to="/">
Login with Facebook
</Form.SmallLink>
</Form.SocialButton>
<Form.Text style={{ marginBottom: "0" }}>
Already a user?{" "}
<Form.RedirectLink to={ROUTES.SIGN_IN}>
Sign in now
</Form.RedirectLink>
.
</Form.Text>
<Form.SmallText>
This page is protected by Google reCAPTCHA to ensure
you're not a bot.{" "}
<Form.SmallLink to="/" style={{ color: "#0071eb" }}>
Learn more
</Form.SmallLink>
.
</Form.SmallText>
</Form.Wrapper>
</Form>
</HeaderContainer>
<FooterContainer />
</>
);
}
signup.jsx
- Sau khi code xong và run thử thì ta sẽ được như hình.

Giải thích một chút về function handleSignUp()
nhé:
- Sau khi send request gồm
email
vàpassword
, nếu đăng ký thành công thì sẽ sang bước tiếp theo. - Sau khi đăng ký thành công sẽ tiến hành update profile gồm
displayName
vàphotoURL
(Avatar) của user. - Phần
photoURL
mình chỉ cho random 1 trong tổng số 5 image mà mình có cho user khi đăng ký thôi 😁.
Nếu chưa có ảnh để làm avatar thì các bạn có thể tải tại đây.
Chúng ta cùng test thử nó có thể Sign Up một user mới được không nhé 😉.
- Nhập thông tin đầy đủ.

- Nếu Sign Up thành công thì nó sẽ tự động redirect sang Browse page.

- Để chắc chắn hơn, chúng ta có thể kiểm tra trên firebase, như hình là đã Sign Up thành công .

II. Cấu hình Router auth.
Tại sao lại config lại phần router? Thì như các bạn biết, việc chia các loại router khác nhau để phân loại các role của user, tức là ta sẽ phân loại ra thành 2 loại user là: user chưa login và user đã login.
Tùy thuộc vào từng case mà user sẽ vào được những page khác nhau, đó là những gì mà chúng ta sẽ config ngay dưới đây. Trong folder src ta tạo thêm folder helpers và file routes.js
như sau:

Trong file routes.js
ta thêm các đoạn code sau:
import React from "react";
import { Route, Redirect } from "react-router-dom";
export function IsUser({ user, LoggedInPath, children, ...rest }) {
return (
<Route
{...rest}
render={() => {
if (!user) {
return children;
}
if (user) {
return <Redirect to={{ pathname: LoggedInPath }} />;
}
return null;
}}
/>
);
}
export function ProtectedRoute({ user, children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) => {
if (user) {
return children;
}
if (!user) {
return (
<Redirect
to={{
pathname: "signin",
state: { from: location }
}}
/>
);
}
return null;
}}
/>
);
}
helpers/routes.js
Giải thích một chút về 2 function trên nhé:
isUser()
: function này có nhiệm vụ là check xem user đã login hay chưa, nếu chưa thì sẽ load page tương ứng là SignIn hoặc SignUp page ngược lại sẽ Redirect đến Browser page.ProtectedRoute()
: function này có nhiệm vụ là chặn những user có những action cố truy cập Browser page hoặc những page khác mà bắt buộc user phải login tài khoản mới có thể truy cập được. Nếu user vẫn cố tình truy cập các page này bằng cách gõ các path tương ứng trên thanh url của browser thì nó sẽ tự động Redirect đến SignIn page theopathname
vàlocation
tương ứng.
Okie! Vậy sử dụng nó ra sao? Thì để sử dụng, chúng ta sẽ import và dùng nó trong file App.js
như sau:
import React from "react";
import { BrowserRouter } from "react-router-dom";
import * as ROUTES from "./constants/routes";
import { Home, SignIn, SignUp, Browse } from "./pages/index";
import { IsUser, ProtectedRoute } from "./helpers/routes"; //<------------ import here
export default function App() {
const user = null;
return (
<BrowserRouter>
{/*Use it here*/}
<IsUser
user={user}
path={ROUTES.SIGN_IN}
LoggedInPath={ROUTES.BROWSE}
>
<SignIn />
</IsUser>
<IsUser
user={user}
path={ROUTES.SIGN_UP}
LoggedInPath={ROUTES.BROWSE}
>
<SignUp />
</IsUser>
<ProtectedRoute user={user} path={ROUTES.BROWSE} exact>
<Browse />
</ProtectedRoute>
<IsUser user={user} path={ROUTES.HOME} LoggedInPath={ROUTES.BROWSE}>
<Home />
</IsUser>
</BrowserRouter>
);
}
App.js
III. Tạo custom hook (auth listener).
Chúng ta sẽ tạo một custom hook để handle việc listen user SignIn hoặc SignOut từ đó có thể lấy được data của user đó để sử dụng cho những việc cần thiết sau này.
Trong folder src ta tạo một folder hooks, dùng để chứa các custom hook. Tiếp theo trong folder hooks vừa tạo, ta tạo thêm file useAuthListener.jsx
và sẽ code như sau:
import React, { useState, useContext, useEffect } from "react";
import { FirebaseContext } from "../context/firebase";
export default function useAuthListener() {
const [user, setUser] = useState(
JSON.parse(localStorage.getItem("authUser"))
);
const { firebase } = useContext(FirebaseContext);
useEffect(() => {
const listener = firebase.auth().onAuthStateChanged((authUser) => {
if (authUser) {
localStorage.setItem("authUser", JSON.stringify(authUser));
setUser(authUser);
} else {
localStorage.removeItem("authUser");
setUser(null);
}
});
return () => listener();
}, [firebase]);
return { user };
}
useAuthListener.jsx
Giải thích:
onAuthStateChanged()
: function này listen khi user trigger việc SignIn hoặc SignOut.- Nếu user SignIn thì lưu user data vào localStorage ngược lại thì remove khỏi localStorage.
Vì trong tương lai, chúng ta sẽ có thêm nhiều custom hook khác, do đó ta sẽ tạo thêm file index.jsx
trong folder hooks và config như sau:
import useAuthListener from "./useAuthListener";
export { useAuthListener };
hooks/index.jsx
Tiếp theo là sử dụng custom hook vừa built xong nè 😁, trong file App.js
chúng ta sẽ sử dụng như sau:
import React from "react";
import { BrowserRouter } from "react-router-dom";
import * as ROUTES from "./constants/routes";
import { Home, SignIn, SignUp, Browse } from "./pages/index";
import { IsUser, ProtectedRoute } from "./helpers/routes";
import { useAuthListener } from "./hooks/index"; //<---------- Import here
export default function App() {
const { user } = useAuthListener(); //<---------- Use it here
return (
<BrowserRouter>
<IsUser
user={user}
path={ROUTES.SIGN_IN}
LoggedInPath={ROUTES.BROWSE}
>
<SignIn />
</IsUser>
<IsUser
user={user}
path={ROUTES.SIGN_UP}
LoggedInPath={ROUTES.BROWSE}
>
<SignUp />
</IsUser>
<ProtectedRoute user={user} path={ROUTES.BROWSE} exact>
<Browse />
</ProtectedRoute>
<IsUser user={user} path={ROUTES.HOME} LoggedInPath={ROUTES.BROWSE}>
<Home />
</IsUser>
</BrowserRouter>
);
}
App.js
Sau khi reload thì có thể các bạn sẽ thắc mắc là tại sao current page là Browser page (như hình dưới), mặc dù chúng ta đã thực hiện việc SignIn đâu?

Trong bài trước chúng ta đã test việc SigIn và SignUp, do đó account mà chúng ta đã SignUp và sử dụng để SignIn ở bài trước nó vẫn còn lưu lại trên firebase.
Tuy nhiên trên firebase nó chưa ghi nhận việc chúng ta đã gọi function signOut()
của nó để SignOut cái account này ra khỏi app, do đó ta mới có trường hợp trên.
Okie current page là Browser page do đó ta có thể thấy custom hook của chúng ta đã hoạt động. Chúng ta check thêm nó có lưu được vào localStorage không nhé 😉.

Perfect!!! 😁
IV. Tổng kết.
Okie phần thứ 7 của series này cũng đã xong, cảm ơn các bạn đã đọc. Hẹn gặp lại các bạn trong phần tiếp theo nhé 😉. See u again~