Cổng thông tin học sinh, sinh viên

Kho tàng kiến thức và tài nguyên học tập hữu ích

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

07 May, 2025 #netflix-clone Javascript • Chill mỗi ngày
Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) -
Phần 7

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.

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
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
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
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
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
helpers folder

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

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?

current page - Browser page
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
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~

⬅ Về trang chủ