Facebook Pixel

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7

08 Dec, 2021

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 😁.

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7

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 headercontainer.

JSX
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.
UI of Sign Up page

Giải thích một chút về function handleSignUp() nhé:

  • Sau khi send request gồm emailpassword, 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 displayNamephotoURL (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 đủ.
In case of full information to register
  • Nếu Sign Up thành công thì nó sẽ tự động redirect sang Browse page.
Sign Up Success
  • Để 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 .
Account has been successfully registered - Firebase

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:

helpers folder

Trong file routes.js ta thêm các đoạn code sau:

JSX
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 theo pathnamelocation 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:

JSX
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:

JSX
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:

JSX
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:

JSX
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?

current page - Browser page

Trong bài trước chúng ta đã test việc SigInSignUp, 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é 😉.

authUser - localStorage

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~

Bài viết liên quan

Lập trình backend expressjs

xây dựng hệ thống microservices
  • Kiến trúc Hexagonal và ứng dụngal font-
  • TypeScript: OOP và nguyên lý SOLIDal font-
  • Event-Driven Architecture, Queue & PubSubal font-
  • Basic scalable System Designal font-

Đăng ký nhận thông báo

Đừng bỏ lỡ những bài viết thú vị từ 200Lab