---
title: "Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 4"
description: "Trong phần 4 của series này, chúng ta sẽ tiếp tục hoàn thiện phần Guest Home Page . Lần này, chúng ta sẽ bắt tay vào code phần FAQs component, thực hiện config router để redirects qua lại các page nhé. Cùng bắt đầu thôi 😉."
canonical: "https://200lab.io/blog/netflix-clone-voi-reactjs-styled-components-va-firebase-firestore-auth-phan-4"
published: "2021-12-03T01:52:00Z"
updated: "2026-08-21T07:17:51Z"
authors: ["TỪ QUỐC HƯNG"]
tags: ["Javascript"]
reading_minutes: 11
access: "public"
---

Trong phần 4 của series này, chúng ta sẽ tiếp tục hoàn thiện phần **Guest Home Page** . Lần này, chúng ta sẽ bắt tay vào code phần **FAQs component**, thực hiện **config router** để _redirects_ qua lại các page nhé. Cùng bắt đầu thôi 😉!

## I. Implement FAQ UI

Okie! Giờ chúng ta bắt đầu implement **FAQ** UI như hình bên dưới.

![FAQ UI](https://assets.200lab.io/images/2024/12/05/image-1-e7a48b760bb59a31e38c.webp)

Trong folder **components** ta tạo tiếp component **FAQs**  như sau:

![FAQs component folder](https://assets.200lab.io/images/2024/12/05/image-2-ec47926d6d6d7a451993.webp)

Bạn có thể lên trang **Home** của Netflix (Nhớ là **Guest Home Page** nha) thì bạn có thể thấy, khi ta click vô icon **`+`** thì nó sẽ dropdown phần content xuống như hình:

![FAQ content](https://assets.200lab.io/images/2024/12/05/image-3-42fd076a925c27181e8c.webp)

Như hình trên, mình cũng đã phân tích một chút về layout của đống này rồi, chúng ta bắt tay vào làm thôi 😁, đầu tiên trong file `FAQs.jsx` chúng ta sẽ style cho những thứ cần thiết như sau:

```JavaScript
import styled from "styled-components/macro";

export const Container = styled.div`
	display: flex;
	border-bottom: 8px solid #222;
	position: relative;
	margin: 0;
	background: 0 0;

	@media only screen and (min-width: 550px) and (max-width: 949px),
		only screen and (min-width: 950px) and (max-width: 1449px),
		only screen and (min-width: 1450px) {
		padding: 70px 45px;
	}
`;

export const Header = styled.div`
	display: flex;
	justify-content: space-between;
	align-items: center;
	cursor: pointer;
	margin-bottom: 1px;
	font-size: 26px;
	font-weight: normal;
	background-color: #303030;
	padding: 0.8em 1.2em 0.8em 1.2em;
	user-select: none;
	transition: all 0.3s ease;

	img {
		filter: brightness(0) invert(1);
		width: 24px;
	}
`;

export const Body = styled.div`
	font-size: 26px;
	font-weight: normal;
	line-height: normal;
	background: #303030;
	white-space: pre-wrap;
	user-select: none;
	overflow: hidden;
	padding: 1.2em;
`;

export const Item = styled.div`
	color: #fff;
	margin: 0 0 8px 0;
`;

export const Frame = styled.div`
	margin-bottom: 40px;
`;

export const InnerWrapper = styled.div`
	max-width: 815px;
	margin: 1.25em auto;

	@media only screen and (min-width: 950px) and (max-width: 1449px),
		only screen and (min-width: 1450px) {
		margin-top: 20px;
		width: 75%;
		margin: 3.25em auto;
	}
`;

export const Inner = styled.div`
	display: flex;
	margin: auto;
	flex-direction: column;
	width: 100%;
`;

export const Title = styled.h1`
	font-size: 3.125rem;
	line-height: 1.1;
	margin-top: 0;
	margin-bottom: 0.5rem;
	color: #fff;
	text-align: center;

	@media screen and (max-width: 600px) {
		font-size: 35px;
	}
`;
```

_FAQs.jsx_

Tiếp đến là `/FAQs/index.jsx`:

```JavaScript
import React, { useState, useContext, createContext } from "react";
import {
	Container,
	Inner,
	Header,
	Title,
	Frame,
	Item,
	Body,
	InnerWrapper
} from "./styles/FAQs";

const toggleContext = createContext();

export default function FAQs({ children, ...restProps }) {
	return (
		<Container {...restProps}>
			<Inner>{children}</Inner>
		</Container>
	);
}

FAQs.Title = function FAQsTitle({ children, ...restProps }) {
	return <Title {...restProps}>{children}</Title>;
};

FAQs.Frame = function FAQsFrame({ children, ...restProps }) {
	return <Frame {...restProps}>{children}</Frame>;
};

FAQs.Item = function FAQsItem({ children, ...restProps }) {
	const [toggleShow, setToggleShow] = useState(false);
	return (
		<toggleContext.Provider value={{ toggleShow, setToggleShow }}>
			<Item {...restProps}>{children}</Item>
		</toggleContext.Provider>
	);
};

FAQs.Header = function FAQsHeader({ children, ...restProps }) {
	const { toggleShow, setToggleShow } = useContext(toggleContext);
	return (
		<Header onClick={() => setToggleShow(!toggleShow)} {...restProps}>
			{children}
			{toggleShow ? (
				<img src="/images/icons/close-slim.png" alt="close" />
			) : (
				<img src="/images/icons/add.png" alt="open" />
			)}
		</Header>
	);
};

FAQs.Body = function FAQsBody({ children, ...restProps }) {
	const { toggleShow } = useContext(toggleContext);
	return toggleShow ? <Body {...restProps}>{children}</Body> : null;
};

FAQs.InnerWrapper = function FAQsInnerWrapper({ children, ...restProps }) {
	return <InnerWrapper {...restProps}>{children}</InnerWrapper>;
};
```

_index.jsx_

Trong folder **containers** ta thêm file `FAQsContainer.jsx` và thêm đoạn code sau:

```JavaScript
import React from "react";
import { FAQs } from "../components/index";
import faqsData from "../fixtures/faqs.json";

export default function FAQsContainer() {
	return (
		<FAQs>
			<FAQs.Title>Frequently Asked Questions</FAQs.Title>
			<FAQs.InnerWrapper>
				{faqsData.map((item) => (
					<FAQs.Item key={item.id}>
						<FAQs.Header>{item.header}</FAQs.Header>
						<FAQs.Body>{item.body}</FAQs.Body>
					</FAQs.Item>
				))}
			</FAQs.InnerWrapper>
		</FAQs>
	);
}
```

_FAQsContainer.jsx_

Trong case này (trong file `/FAQs/index.jsx`) mình dùng **context** để truyền đi những data cần thiết để chúng có thể giao tiếp với nhau. Như ở phần 1 mình đã trình bày, trong bài này chúng ta sẽ không đề cập sâu vào thằng **context** của react do đó mình chỉ giải thích sơ bộ về cách hoạt động của component trên với **context** nhé 😉.

Đầu tiên,  `Item` là phần sẽ **wrap** bên ngoài của phần `Header` và `Body`, do đó khi click vào `Header` thì phần `Body` sẽ được show ra bên dưới `Header` và đồng thời icon của `Header` cũng thay đổi tương ứng.

Mình đã tạo một **context** có tên là `toggleContext` để lưu trữ nữa data phục vụ cho việc này, trong method `Item` mình tạo một **state** là `toggleShow` sau đó dùng `<toggleContext.Provider>` wrap bên ngoài `Item` và truyền giá trị của **state** vào thông qua **value**.

Lúc này trong phần `Header` ta sẽ lấy giá trị từ **context** và sử dụng chúng để set value `true/flase` (open or close). Trong phần `Body` ta chỉ cần lấy giá trị **context** ra để handle là xong.

_**Demo:**_

- _close._

![FAQ Close](https://assets.200lab.io/images/2024/12/05/image-4-02c21f8ebda1fe1df73b.webp)

- _Open._

![FAQ Opened](https://assets.200lab.io/images/2024/12/05/image-5-cac5e8592334f5ce75d4.webp)

Tiếp theo, chúng ta sẽ làm phần **Form** đăng ký member trong phần của** FAQs** luôn nhé 😉.

- Tạo các file và thư mục như sau:

![OptionForm component folder](https://assets.200lab.io/images/2024/12/05/image-6-a4f2bda122a8d26f89ee.webp)

- Trong file `OptionForm.jsx`, ta code như sau:

```JavaScript
import styled from "styled-components/macro";

export const Container = styled.div`
	display: flex;
	justify-content: center;
	flex-direction: column;
	height: 100%;
	padding-top: 0.85rem;
	flex-wrap: wrap;
	@media (max-width: 1000px) {
		flex-direction: column;
		align-items: center;
	}

	@media only screen and (min-width: 550px) and (max-width: 949px),
		only screen and (min-width: 950px) and (max-width: 1449px),
		only screen and (min-width: 1450px) {
		padding: 70px 45px;
	}
`;

export const Button = styled.button`
	display: flex;
	align-items: center;
	height: 60px;
	background-color: #e50914;
	color: white;
	text-transform: capitalize;
	padding: 0 1em;
	font-size: 1.625rem;
	border: 0;
	border-radius: 2px;
	border-top-left-radius: 0;
	border-bottom-left-radius: 0;
	border-left: 1px solid #333;
	cursor: pointer;
	img {
		margin-left: 10px;
		filter: brightness(0) invert(1);
		width: 24px;
		@media (max-width: 1000px) {
			width: 16px;
		}
	}
	&:hover {
		background: #f40612;
	}
`;

export const Input = styled.input`
	max-width: 450px;
	width: 100%;
	border: 0;
	padding: 10px;
	height: 60px;
	box-sizing: border-box;
	font-size: 16px;
	border-radius: 2px;
	border-top-right-radius: 0;
	border-bottom-right-radius: 0;
`;

export const Text = styled.p`
	font-size: 19.2px;
	color: white;
	text-align: center;
	padding-top: 0.85rem;
	@media (max-width: 600px) {
		font-size: 16px;
		line-height: 22px;
	}
`;

export const Break = styled.div`
	flex-basis: 100%;
	height: 0;
`;

export const WrapContent = styled.div`
	position: relative;
	width: 100%;
	padding: 75px 0;
	max-width: 950px;
	margin: 0 auto;
	text-align: center;
	z-index: 1;
	font-size: 1.625rem;
	font-weight: 400;
	color: #fff;
`;

export const WrapForm = styled.form`
	text-align: center;

	@media only screen and (min-width: 950px) and (max-width: 1449px),
		only screen and (min-width: 1450px) {
		display: flex;
		flex-direction: row;
		justify-content: center;
		margin-bottom: 1rem;
	}
`;

export const WrapFormFAQ = styled.div`
	display: flex;
	flex-direction: column;
	margin: 0 auto;
	width: 100%;
	max-width: 950px;
`;

export const Title = styled.h1`
	max-width: 640px;
	margin: 0 auto;
	line-height: 1.1;
	font-size: 1.75rem;

	@media only screen and (min-width: 950px) and (max-width: 1449px),
		only screen and (min-width: 1450px) {
		font-size: 4rem;
	}

	@media only screen and (min-width: 950px) and (max-width: 1449px) {
		font-size: 3.125rem;
	}

	@media only screen and (min-width: 1450px) {
		max-width: 800px;
	}
`;

export const SubTitle = styled.h2`
	font-size: 1.125rem;
	margin: 1rem auto;
	max-width: 640px;
	font-weight: 400;

	@media only screen and (min-width: 950px) and (max-width: 1449px),
		only screen and (min-width: 1450px) {
		font-size: 1.625rem;
	}
`;
```

_OptionForm.jsx_

- Trong `/OptionForm/index.jsx`, ta thêm đoạn code như sau:

```JavaScript
import React from "react";
import {
	Container,
	Button,
	Input,
	Text,
	Break,
	Title,
	SubTitle,
	WrapForm,
	WrapContent,
	WrapFormFAQ
} from "./styles/OptionForm";

export default function OptionForm({ children, ...restProps }) {
	return <Container {...restProps}>{children}</Container>;
}

OptionForm.Input = function OptionFormInput({ ...restProps }) {
	return <Input {...restProps} />;
};

OptionForm.Text = function OptionFormText({ children, ...restProps }) {
	return <Text {...restProps}>{children}</Text>;
};

OptionForm.Button = function OptionFormButton({ children, ...restProps }) {
	return (
		<Button {...restProps}>
			{children}{" "}
			<img src="/images/icons/chevron-right.png" alt="Finish Sign Up" />
		</Button>
	);
};

OptionForm.Break = function OptionFormBreak({ ...restProps }) {
	return <Break {...restProps} />;
};

OptionForm.WrapForm = function OptionFormWrapForm({ children, ...restProps }) {
	return <WrapForm {...restProps}>{children}</WrapForm>;
};

OptionForm.WrapFormFAQ = function OptionFormWrapFormFAQ({
	children,
	...restProps
}) {
	return <WrapFormFAQ {...restProps}>{children}</WrapFormFAQ>;
};

OptionForm.WrapContent = function OptionFormWrapContent({
	children,
	...restProps
}) {
	return <WrapContent {...restProps}>{children}</WrapContent>;
};

OptionForm.Title = function OptionFormTitle({ children, ...restProps }) {
	return <Title {...restProps}>{children}</Title>;
};

OptionForm.SubTitle = function OptionFormSubTitle({ children, ...restProps }) {
	return <SubTitle {...restProps}>{children}</SubTitle>;
};
```

_index.jsx_

Vì Button này thuộc phần **FAQs** luôn nên ta sẽ thêm nó vào trong file `FAQsContainer.jsx` luôn nhé.

```JavaScript
import React from "react";
import { FAQs, OptionForm } from "../components/index";
import faqsData from "../fixtures/faqs.json";

export default function FAQsContainer() {
	return (
		<FAQs>
			<FAQs.Title>Frequently Asked Questions</FAQs.Title>
			<FAQs.InnerWrapper>
				{faqsData.map((item) => (
					<FAQs.Item key={item.id}>
						<FAQs.Header>{item.header}</FAQs.Header>
						<FAQs.Body>{item.body}</FAQs.Body>
					</FAQs.Item>
				))}
			</FAQs.InnerWrapper>
			<OptionForm.WrapFormFAQ>
				<OptionForm.Text>
					Ready to watch? Enter your email to create or restart your
					membership.
				</OptionForm.Text>
				<OptionForm.WrapForm>
					<OptionForm.Input placeholder="Email address" />
					<OptionForm.Button>Get Started</OptionForm.Button>
				</OptionForm.WrapForm>
			</OptionForm.WrapFormFAQ>
		</FAQs>
	);
}
```

_FAQsContainer.jsx_

Okie! xong, chúng ta run thử xem thế nào.

![FAQs UI demo](https://assets.200lab.io/images/2024/12/05/image-7-8ee52663e798754f1e62.webp)

Okie đẹp rồi nhé 😁.

## II. Config router và phân chia các trang.

### 1. Config router.

Trước tiên ta cần cài thêm package `react-router-dom` để có thể config `router` nhé `npm install react-router-dom`

Sau khi install xong, trong folder **src** ta tạo folder và file như sau `/constants/routes.js` và trong file `routes.js` ta config như sau:

```JavaScript
export const HOME = "/";
export const SIGN_UP = "/signup";
export const SIGN_IN = "/signin";
export const BROWSE = "/browse";
```

_routes.js_

Chúng ta chỉnh sửa một chút trong **containers** nhé, trong folder **containers** ta tạo thêm `index.jsx` và config như sau:

```JavaScript
import StoryContainer from "./storyContainer";
import FAQsContainer from "./FAQsContainer";
import FooterContainer from "./footerContainer";

export { StoryContainer, FAQsContainer, FooterContainer };
```

_index.jsx_

Trong file `App.js` ta edit và thêm đoạn code như sau:

```JavaScript
import React from "react";
import {
	StoryContainer,
	FAQsContainer,
	FooterContainer
} from "./containers/index";
import { BrowserRouter, Route } from "react-router-dom";
import * as ROUTES from "./constants/routes";

export default function App() {
	return (
		<BrowserRouter>
			<Route exact path={ROUTES.HOME}>
				<StoryContainer />
				<FAQsContainer />
				<FooterContainer />
			</Route>
			<Route exact path="/users">
				<h1>User Page</h1>
			</Route>
		</BrowserRouter>
	);
}
```

_App.js_

### 2. Phân chia các trang.

Một website thông thường sẽ có rất nhiều page, để website có thể **redirects** sang các page khác thì sẽ tuân theo một vài điều kiện nhất định. Đối với **Netflix-clone** hiện tại của chúng ta, mình sẽ chia trước các page như sau: **Guest Home**, **SignUp**, **SignIn** và **Browse** (**Browse** là gì thì mình sẽ nói sau nhé).

Trong folder **src** ta tạo thêm file như sau:

- `/src/pages/home.jsx`(Nếu có rồi thì thôi nhé):

```JavaScript
import React from "react";
import {
	StoryContainer,
	FAQsContainer,
	FooterContainer
} from "../containers/index";

export default function Home() {
	return (
		<>
			<StoryContainer />
			<FAQsContainer />
			<FooterContainer />
		</>
	);
}
```

_home.jsx_

- `/src/pages/signup.jsx`:

```JavaScript
import React from "react";

export default function SignUp() {
	return <h1>Sign Up</h1>;
}
```

_signup.jsx_

- `/src/pages/signin.jsx`:

```JavaScript
import React from "react";

export default function SignIn() {
	return <h1>Sign In</h1>;
}
```

_signin.jsx_

- `/src/pages/browse.jsx`:

```JavaScript
import React from "react";

export default function Browse() {
	return <h1>Browse</h1>;
}
```

_browse.jsx_

- `/src/pages/index.jsx`:

```JavaScript
import Home from "./home";
import SignIn from "./signin";
import SignUp from "./signup";
import Browse from "./browse";

export { Home, SignIn, SignUp, Browse };
```

_index.jsx_

- Trong file `App.js` ta edit lại như sau:

```
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import * as ROUTES from "./constants/routes";
import { Home, SignIn, SignUp, Browse } from "./pages/index";

export default function App() {
	return (
		<BrowserRouter>
			<Route exact path={ROUTES.SIGN_IN}>
				<SignIn />
			</Route>
			<Route exact path={ROUTES.SIGN_UP}>
				<SignUp />
			</Route>
			<Route exact path={ROUTES.BROWSE}>
				<Browse />
			</Route>
			<Route exact path={ROUTES.HOME}>
				<Home />
			</Route>
		</BrowserRouter>
	);
}
```

Okie! Cơ bản như vậy đã xong, thử xem work không nhé 😁.

![Redirect to another page (Sign In)](https://assets.200lab.io/images/2024/12/05/image-8-8aec2118632cc8f77379.webp)

## III. Tổng kết.

Okie như vậy là đã xong, trong bài này, chúng ta đã code và hoàn thiện được phần **FAQs** của trang, apply **context** vào để quản lý các **state**, **config router** và phân chia các page cho app của chúng ta. Trong phần tiếp theo chúng ta sẽ hoàn thiện **Guest Home page** này luôn nhé 😉. Cảm hơn các bạn đã đọc! See u again!

## Đọc thêm

- [Event Loop là gì? Cơ chế hoạt động của Event Loop trong JavaScript](https://200lab.io/blog/event-loop-la-gi.md): Event Loop là một cơ chế cho phép xử lý các tác vụ bất đồng bộ trong JavaScript, sử dụng một vòng lặp vô tận để kiểm tra, quản lý các tác vụ.
- [So sánh Golang và NodeJS chi tiết](https://200lab.io/blog/so-sanh-golang-vs-nodejs.md): Golang và NodeJS khác nhau như thế nào? Golang và Nodejs từ lâu đã trở thành chủ đề sôi nổi. Nodejs đã chiếm ưu thế trong một khoảng thời gian dài. Golang liệu có đủ sức thuyết phục để chuyển đổi?
- [ReactJS Tutorial for Beginners Phần 2](https://200lab.io/blog/reactjs-tutorial-for-beginners-phan-2.md): Trong bài viết này chúng ta sẽ đi qua năm nội dung đầu tiên của tutorial
- [ReactJS vs React Native - Gà cùng một mẹ liệu có giống nhau?](https://200lab.io/blog/reactjs-vs-react-native.md): Bạn có đang rối giữa ReactJS vs React Native? Nên chọn cái nào thì tốt hơn? Có thể tái sử dụng code của ReactJS cho React Native hay không? Hãy khám phá câu trả lời thông qua bài viết so sánh ReactJS vs React Native này bạn nhé!
- [Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7](https://200lab.io/blog/netflix-clone-voi-reactjs-styled-components-va-firebase-firestore-auth-phan-7.md): 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 😁.
- [Mọi bài viết chủ đề Javascript](https://200lab.io/blog/tags/javascript.md)

Toàn bộ nội dung: https://200lab.io/llms.txt
