Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 6
05 Dec, 2021
TỪ QUỐC HƯNG
AuthorTrong phần này, chúng ta sẽ config React Context firebase cho project của chúng ta, sau đó tạo Sign In Page và apply Authentication màfirebase cung cấp cho project của chúng ta nhé. Bắt đầu thôi 😉!

Mục Lục
Trong phần trước, chúng ta đã hoàn thành trang Home (Guest Home Page), đồng thời cũng đã config và connect được với firebase. Trong phần phần này, chúng ta sẽ config React Context firebase, sau đó tạo Sign In Page và apply Authentication mà firebase cung cấp cho project của chúng ta nhé. Bắt đầu thôi 😉!
I. React Context for firebase.
Để các component có sử dụng các data do firebase trả về mà không cần truyền data theo kiểu nested từ cha → con, một kiểu truyền thống (phức tạp) thì chúng ta sẽ tạo một context có cho firebase như sau:
- Trong folder src ta tạo folder và file như sau:
/src/context/firebase.js
import { createContext } from "react";
export const FirebaseContext = createContext(null);
firebase.js
- Trong file
/src/index.js
, ta edit lại như sau:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { GlobalStyles } from "./global-styles";
import { firebase } from "./lib/firebase.prod";
import { FirebaseContext } from "./context/firebase"; //<----- here
ReactDOM.render(
<React.StrictMode>
<FirebaseContext.Provider value={{ firebase }}> {/* <----- here */}
<GlobalStyles />
<App />
</FirebaseContext.Provider>
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals>
reportWebVitals();
src/index.js

Okie! Cơ bản như vậy đã xong, giờ chúng ta sang bước tiếp theo, tạo Sign In Page nhé 😉.
II. Tạo Sign In page.
Sign In page thì cũng có phần header và footer như bên trang Home thôi.
Trong file /pages/signin.jsx
, ta thêm đoạn code như sau:
import React from "react";
import { HeaderContainer, FooterContainer } from "../containers/index";
export default function SignIn() {
return (
<>
<HeaderContainer>
</HeaderContainer>
<FooterContainer />
</>
);
}
signin.jsx
Ta sẽ được như hình ở dưới đây:

Okie, So sánh phần UI Sign In của Netflix real và Netflix clone của chúng ta hiện tại thì sẽ có 2 thứ phải làm:
- Đầu tiên là phần form Sign In,
- Thứ 2 là phần Select box và button Sign In ở header, chúng ta sẽ ẩn nó đi ở Sign In page này.
Phần form để Sign In và cách để ẩn Select box và button Sign In, ta thực hiện như sau:
- Trong folder components tạo thêm foder và file như sau:

- Trong file
form.jsx
ta code như sau:
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
export const Container = styled.div`
display: flex;
flex-direction: column;
background-color: rgba(0, 0, 0, 0.75);
border-radius: 4px;
box-sizing: border-box;
min-height: 515px;
width: 100%;
margin: auto;
max-width: 450px;
padding: 20px 0 30px;
@media only screen and (min-width: 500px) {
min-width: 380px;
}
@media only screen and (min-width: 740px) {
min-height: 660px;
padding: 60px 68px 40px;
margin-bottom: 90px;
}
`;
export const ErrorMessage = styled.div`
background-color: #e87c03;
border-radius: 4px;
font-size: 14px;
margin: 0 0 14px;
color: #fff;
padding: 15px 20px;
`;
export const Base = styled.form`
display: flex;
flex-direction: column;
width: 100%;
flex-grow: 0.5;
`;
export const Title = styled.h1`
color: #fff;
font-size: 32px;
font-weight: 700;
margin-bottom: 28px;
padding: 0;
`;
export const Text = styled.p`
color: #737373;
font-size: 16px;
font-weight: 500;
`;
export const SmallText = styled.span`
margin-top: 10px;
font-size: 13px;
line-height: normal;
color: #8c8c8c;
`;
export const RedirectLink = styled(Link)`
color: #fff;
text-decoration: none;
&:hover {
text-decoration: underline;
}
`;
export const SmallLink = styled(RedirectLink)`
color: #b3b3b3;
cursor: pointer;
font-size: 13px;
font-weight: 500;
padding-top: 2px;
`;
export const Input = styled.input`
background: #333;
border-radius: 4px;
border: 0;
color: #fff;
height: 50px;
line-height: 50px;
padding: 5px 20px;
width: 100%;
box-shadow: none;
box-sizing: border-box;
font-size: 16px;
display: block;
appearance: none;
margin-bottom: 16px;
`;
export const Button = styled.button`
border: 0;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
margin: 24px 0 12px;
background: #e50914;
position: relative;
box-shadow: 0 1px 0 rgb(0 0 0 / 55%);
color: #fff;
cursor: pointer;
letter-spacing: 0.1px;
user-select: none;
text-align: center;
@media only screen and (min-width: 360px) {
width: 100%;
max-width: 100%;
}
@media only screen and (min-width: 500px) {
padding: 16px;
display: inline-block;
min-width: 98px;
min-height: 37px;
line-height: 1em;
&:disabled {
opacity: 0.8;
}
}
`;
export const CheckBox = styled.input`
width: 16px;
height: 16px;
`;
export const Wrapper = styled.div`
display: flex;
align-items: center;
`;
export const FlexBox = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`;
export const SocialButton = styled.button`
margin-top: 16px;
min-height: 0;
width: auto;
border: 0;
background: 0 0;
box-shadow: none;
padding: 0;
margin: 0;
`;
export const Image = styled.img`
width: 20px;
height: 20px;
margin-right: 10px;
vertical-align: middle;
`;
form.jsx
- Trong file
/form/index.jsx
, ta code như sau:
import React from "react";
import {
Container,
Base,
ErrorMessage,
Title,
Text,
SmallText,
RedirectLink,
Input,
Button,
CheckBox,
Wrapper,
FlexBox,
SmallLink,
SocialButton,
Image
} from "./styles/form";
export default function Form({ children, ...restProps }) {
return <Container {...restProps}>{children}</Container>;
}
Form.Base = function FormBase({ children, ...restProps }) {
return <Base {...restProps}>{children}</Base>;
};
Form.ErrorMessage = function FormErrorMessage({ children, ...restProps }) {
return <ErrorMessage {...restProps}>{children}</ErrorMessage>;
};
Form.Title = function FormTitle({ children, ...restProps }) {
return <Title {...restProps}>{children}</Title>;
};
Form.Text = function FormText({ children, ...restProps }) {
return <Text {...restProps}>{children}</Text>;
};
Form.SmallText = function FormSmallText({ children, ...restProps }) {
return <SmallText {...restProps}>{children}</SmallText>;
};
Form.RedirectLink = function FormRedirectLink({ children, ...restProps }) {
return <RedirectLink {...restProps}>{children}</RedirectLink>;
};
Form.SmallLink = function FormSmallLink({ children, ...restProps }) {
return <SmallLink {...restProps}>{children}</SmallLink>;
};
Form.Input = function FormInput({ children, ...restProps }) {
return <Input {...restProps}>{children}</Input>;
};
Form.Button = function FormButton({ children, ...restProps }) {
return <Button {...restProps}>{children}</Button>;
};
Form.SocialButton = function FormSocialButton({ children, ...restProps }) {
return <SocialButton {...restProps}>{children}</SocialButton>;
};
Form.Image = function FormImage({ children, ...restProps }) {
return <Image {...restProps}>{children}</Image>;
};
Form.CheckBox = function FormCheckBox({ children, ...restProps }) {
return <CheckBox {...restProps}>{children}</CheckBox>;
};
Form.Wrapper = function FormWrapper({ children, ...restProps }) {
return <Wrapper {...restProps}>{children}</Wrapper>;
};
Form.FlexBox = function FormFlexBox({ children, ...restProps }) {
return <FlexBox {...restProps}>{children}</FlexBox>;
};
form/index.jsx
- Tiếp đến là file
/pages/signin.jsx
, chúng ta code như sau:
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 SignIn() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const { firebase } = useContext(FirebaseContext);
const history = useHistory();
// Check form input elements are valid
const isInvalid = password === "" || email === "";
// Email & Password
const handleSignIn = (event) => {
event.preventDefault();
//Apply authentication of firebase
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
// Redirect to browse page
history.push(ROUTES.BROWSE);
})
.catch((error) => {
setErrorMessage(error.code);
});
};
return (
<>
<HeaderContainer status="hidden">
<Form>
<Form.Title>Sign In</Form.Title>
{errorMessage && (
<Form.ErrorMessage>{errorMessage}</Form.ErrorMessage>
)}
<Form.Base onSubmit={handleSignIn} method="POST">
<Form.Input
placeholder="Email or phone number"
value={email}
onChange={({ target }) => setEmail(target.value)}
/>
<Form.Input
type="password"
autoComplete="off"
placeholder="Password"
value={password}
onChange={({ target }) => setPassword(target.value)}
/>
<Form.Button disabled={isInvalid} type="submit">
Sign In
</Form.Button>
<Form.FlexBox>
<Form.Wrapper>
<Form.CheckBox
type="checkbox"
defaultChecked={true}
/>
<Form.SmallText style={{ marginTop: "0" }}>
{" "}
Remember me
</Form.SmallText>
</Form.Wrapper>
<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" }}>
New to Netflix?{" "}
<Form.RedirectLink to={ROUTES.SIGN_UP}>
Sign up 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 />
</>
);
}
signin.jsx
Giải thích một tý cho code của file ở trên nhé:
State
: Ta có 3 state gồm:email
,password
vàerrorMessage
.isInvalid
: Đơn giản, chỉ check khi user nhập đầy đủ email và password thì mới hiện rõ button Sign In cho người dùng nhấn.handleSignIn()
: Function handle việc Sign In, nó sẽ send request lên firebase gồmemail
vàpassword
để Sign In. Nếu thành công thì sẽ redirect đến Browse page, ngược lại sẽ show message error lên view.status="signin"
: Ở phầnHeaderContainer
, mình có truyền prop làstatus
vào header để check khi redirect sang Sign In page rồi thì ẩn phần select box và button Sign In đi.
Trong file headerContainer.jsx
ta edit lại như sau:
import React from "react";
import { Header } from "../components/index";
import * as ROUTES from "../constants/routes";
export default function HeaderContainer({ children, status }) {
return (
<Header>
<Header.Frame>
<Header.Wrapper>
<Header.Logo
to={ROUTES.HOME}
alt="Netflix-clone"
src="/images/logo.svg"
/>
{/* Handle hidden or show is here */}
{status === "signin" ? (
""
) : (
<Header.WrapBlock>
<Header.WrapSelect>
<Header.Select>
<Header.Option value="/vn-en/">
English
</Header.Option>
<Header.Option value="/vn/">
Vietnamese
</Header.Option>
</Header.Select>
</Header.WrapSelect>
<Header.ButtonLink to={ROUTES.SIGN_IN}>
Sign In
</Header.ButtonLink>
</Header.WrapBlock>
)}
</Header.Wrapper>
</Header.Frame>
{children}
</Header>
);
}
headerContainer.jsx
Okie! Sau khi xong, chúng ta sẽ được UI như hình:

Để có thể sử dụng được chức năng Sign In bằng email và password thì ta phải đăng ký với firebase trước, để đăng ký các bạn làm như sau:
- Step 1.

- Step 2.

Kết quả được như hình là đã thành công rồi.

Vì ban đầu chưa có Sign Up page để ta đăng ký account do đó ta chưa có thể Sign In lúc này. Tuy nhiên, để test thử việc Sign In bằng email và password với firebase có được hay không, chúng ta sẽ add trực tiếp 1 user vào Authentication của firebase như sau:
Step 1.

Step 2: Thêm email và password (Tùy ý các bạn).

Kết quả sẽ được như hình.

Chúng ta sẽ test thử 2 trường hợp là nhập đúng email, password và sai email hoặc password nhé.
Nhập đúng email và password:


Nhập sai email.

III. Tổng kết.
Okie! Như vậy là đã xong phần thứ 6 của series này. Trong phần này, mình đã hướng dẫn các bạn setup React Context cho firebase, phân thích cũng như code nên Sign In page hoàn chỉnh, giống 99.99% so với hàng real 😁, setup authentication cho project của chúng ta và test chức năng Sign In. Trong phần tiếp theo, chúng ta sẽ làm Sign Up page, config lại phần router để đáp ứng được vấn đề Authentication và nhiều thứ khác. Cảm ơn các bạn đã đọc 😊, See u again~