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