Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 3
02 Dec, 2021
TỪ QUỐC HƯNG
AuthorTiếp tục công việc đang dang dở ở phần 2, trong phần 3 này chúng ta sẽ làm phần Footer ở Guest Home Page này, kèm theo đó chúng ta sẽ apply các tool như ESLint và Prettier vào project của chúng ta nhé.

Mục Lục
Tiếp tục công việc đang dang dở ở phần 2, trong phần 3 này chúng ta sẽ làm phần Footer ở Guest Home Page này, kèm theo đó chúng ta sẽ apply các tool như ESLint và Prettier vào project của chúng ta nhé.
I. Apply normalize.css, ESLint và Prettier.
1. Apply normalize.css.
Chúng ta đã install normalize.css
trước đó rồi, nên giờ chúng ta chỉ việc import
nó vào nữa thôi là được. Chúng ta sẽ import
nó vào trong file index.js
như đường dẫn sau:
Trong src/index.js
: import 'normalize.css';
2. Apply ESLint và Prettier.
Trước tiên nếu các bạn chưa cài các extension ESLint và Prettier trên Visual studio code của mình thì các bạn cài 2 cái đó trước nhé, mình sẽ để hình bên dưới.


Tiếp theo là setting cho workspace của chúng ta. Mặc định nếu ta không setting ở workspace thì VS code sẽ apply setting ở user cho chúng ta luôn. Chúng ta sẽ dùng file Json để setting cho nhanh nhé 😉.
Tạo file theo đường dẫn sau ở folder chính: .vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
settings.json
Note:
"editor.defaultFormatter"
: Chúng ta sẽ chọn default formatter là Prettier (bạn có thể sửa lại formatter khác cũng được nhé)."editor.formatOnSave"
: set bằngtrue
để khi ta save thì nó sẽ tự động format code của chúng ta theo Prettier mà ta đã define ở trên (Nếu không thích thì set thànhfalse
nhé)."source.fixAll.eslint"
: set bằngtrue
, tức là khi save thì ESLint sẽ auto format code và fix các lỗi liên quan đến convention luôn cho chúng ta (Nếu không thích thì set thànhfalse
).
Tiếp theo, các bạn cài cho mình thêm 1 extension nữa như hình bên dưới.

Sau đó, ta tạo file .editorconfig
trong folder chính của project của mình nhé, sau đó thêm đoạn setting bên dưới.
[*]
end_of_line = lf
indent_style = tab
indent_size = 4
.editorconfig
Note: EditorConfig có tác dụng là thống nhất các chuẩn format giữa các editor khác nhau. EditorConfig giúp code chúng ta có tính thống nhất, dễ đọc và bảo trì. Khi cài extension trên thì nó sẽ tự apply file .editorconfig
mà ta đã tạo.
(Các bạn có thể đọc thêm docs của các extension để hiểu rõ hơn nhé).
Các bạn cài thêm package này: npm i prettier eslint-config-prettier eslint-plugin-prettier -D
. Nó có tác dụng tắt các rule không cần thiết hoặc dễ xung đột với Prettier.
Trong phần scripts
của file package.json
ta thêm đoạn scripts như sau:
"lint": "eslint --ext src/**/*.js,*.jsx,*.ts,*.tsx",
"lint:fix": "eslint --fix --ext src/**/*.js,*.jsx,*.ts,*.tsx",
"prettier": "prettier --check \"src/**/(*.tsx|*.ts|*.jsx|*.js|*.scss|*.css)\"",
"prettier:fix": "prettier --write \"src/**/(*.tsx|*.ts|*.jsx|*.js|*.scss|*.css)\""
package.json
Đoạn script trên dùng cho cả Typescript và Javascript nên nếu bạn không thích thì có thể bỏ những phần liên quan đến .ts
hay .tsx
đi nhé.
Tại sao lại thêm đoạn scripts trên? Okie với đoạn scripts trên ta chỉ cần gõ lệnh npm run lint
thì ESLint sẽ auto scan toàn bộ project của chúng ta để thìm các lỗi, sau đó ta chỉ cần gõ npm run lint:fix
thì nó sẽ auto fix toàn bộ file nào lỗi mà nó scan được trong project của chúng ta. Tương tự với ông thần Prettier.
Tiếp theo tạo thêm file .prettierrc
ở folder chính của project để set các rules cho thằng Prettier
{
"arrowParens": "always",
"semi": true,
"trailingComma": "none",
"endOfLine": "lf",
"tabWidth": 4,
"useTabs": true
}
.prettierrc
Tạo thêm file .prettierignore
nó tương tự như thằng .gitignore
nhưng cái này dành cho Prettier, để tránh format các file không cần thiết.
.cache
package-lock.json
.prettierignore
Ta tạo thêm file .eslintrc
để cấu hình thêm cho thằng ESLint.
{
"extends": ["react-app", "prettier"],
"plugins": ["react", "prettier"],
"rules": {
"prettier/prettier": [
"warn",
{
"arrowParens": "always",
"semi": true,
"trailingComma": "none",a
"endOfLine": "lf",
"tabWidth": 4,
"useTabs": true
}
],
"no-console": "warn"
}
}
.eslintrc
Cuối cùng ta thêm file .eslintignore
cho ESlint biết không cần bắt lỗi với những file được khai báo trong này.
/src/serviceWorker.js
/src/setupTests.js.js
.eslintignore
II. Implement Footer UI.
Trong folder src ta tạo thêm folder containers và pages, trong folder containers tạo thêm file storyContainer.jsx
, folder pages tạo thêm file home.jsx
. Tiếp theo, trong folder components ta thêm file index.jsx
.
Tại sao ta lại thêm file này? Thì file này nhằm mục đích là sẽ dùng để gọm các component của chúng ta vào 1 nơi để quản lý, khi nào cần sử dụng thì ta sẽ import
theo dạng Alias thì sẽ gọn gàng và dễ quản lý hơn.
- Trong
/components/index.jsx
:
import Story from "./story/index";
export { Story };
index.jsx
- Trong
/containers/storyContainer.jsx
ta copy từApp.js
và edit lại như sau:
import React from "react";
import { Story } from "../components/index";
import storiesData from "../fixtures/stories.json";
export default function StoryContainer() {
return (
<Story.Container>
{storiesData.map((story) => (
<Story key={story.id} direction={story.direction}>
<Story.BlockPane>
<Story.Title>{story.title}</Story.Title>
<Story.SubTitle>{story.subTitle}</Story.SubTitle>
</Story.BlockPane>
{story.image ? (
<Story.BlockPane>
<Story.Image
src={story.image}
alt={story.alt}
></Story.Image>
</Story.BlockPane>
) : (
""
)}
</Story>
))}
</Story.Container>
);
}
storyContainer.jsx
Note: Chú ý kiểu import
của component Story nhé😉.
- Trong file
App.js
ta edit lại như sau:
import React from "react";
import StoryContainer from "./containers/storyContainer";
export default function App() {
return <StoryContainer />;
}
App.js
Giờ mới thực sự implement phần Footer nè hehe 😁, ta tạo component Footer như hình:

- Trong file
footer.jsx
, ta thêm đoạn code sau:
import styled from "styled-components/macro";
export const Container = styled.div`
display: flex;
flex-direction: column;
margin: auto;
padding: 70px 45px;
max-width: 1000px;
@media screen and (max-width: 1000px) {
padding: 70px 30px;
}
`;
export const Wrapper = styled.div`
display: inline-block;
position: relative;
margin-bottom: 1rem;
`;
export const Column = styled.div`
display: flex;
flex-direction: column;
text-align: left;
`;
export const Row = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
grid-gap: 15px;
@media screen and (max-width: 1000px) {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
`;
export const Link = styled.a`
color: #757575;
margin-bottom: 20px;
font-size: 13px;
text-decoration: none;
&:hover {
text-decoration: underline;
}
`;
export const Title = styled.p`
font-size: 16px;
color: #757575;
margin-bottom: 40px;
cursor: pointer;
&:hover {
text-decoration: underline;
}
`;
export const Text = styled.p`
font-size: 13px;
color: #757575;
margin-bottom: 40px;
`;
export const Break = styled.div`
flex-basis: 100%;
height: 0;
`;
export const SelectWrap = styled.div`
position: relative;
display: inline-block;
width: 100%;
&::before {
content: "\f0ac";
font-family: "Font Awesome 5 Free";
font-weight: 900;
position: absolute;
top: 15px;
left: 15px;
color: #999;
font-size: 16px;
pointer-events: none;
}
`;
export const Select = styled.select`
text-indent: 0;
line-height: 1.7;
background-color: #000;
background-image: none;
border: 1px solid #333;
color: #999;
font-size: 16px;
padding: 12px 26px 12px 45px;
border-radius: 2px;
`;
export const Option = styled.option``;
footer.jsx
- Trong
/footer/index.jsx
ta thêm đoạn code như sau:
import React from "react";
import {
Column,
Container,
Row,
Link,
Title,
Text,
Break,
Select,
Option,
Wrapper,
SelectWrap
} from "./styles/footer";
export default function Footer({ children, ...restProps }) {
return <Container {...restProps}>{children}</Container>;
}
Footer.Wrapper = function FooterWrapper({ children, ...restProps }) {
return <Wrapper {...restProps}>{children}</Wrapper>;
};
Footer.Row = function FooterRow({ children, ...restProps }) {
return <Row {...restProps}>{children}</Row>;
};
Footer.Column = function FooterColumn({ children, ...restProps }) {
return <Column {...restProps}>{children}</Column>;
};
Footer.Link = function FooterLink({ children, ...restProps }) {
return <Link {...restProps}>{children}</Link>;
};
Footer.Title = function FooterTitle({ children, ...restProps }) {
return <Title {...restProps}>{children}</Title>;
};
Footer.Text = function FooterText({ children, ...restProps }) {
return <Text {...restProps}>{children}</Text>;
};
Footer.Break = function FooterBreak({ children, ...restProps }) {
return <Break {...restProps}>{children}</Break>;
};
Footer.SelectWrap = function FooterSelectWrap({ children, ...restProps }) {
return <SelectWrap {...restProps}>{children}</SelectWrap>;
};
Footer.Select = function FooterSelect({ children, ...restProps }) {
return <Select {...restProps}>{children}</Select>;
};
Footer.Option = function FooterOption({ children, ...restProps }) {
return <Option {...restProps}>{children}</Option>;
};
index.jsx
Okie! tiếp theo chúng ta sẽ implement Footer, trong folder containers ta tạo thêm file footerContainer.jsx
, trong file này chúng ta sẽ code như sau:
import { React } from "react";
import { Footer } from "../components/index";
export default function FooterContainer() {
return (
<Footer>
<Footer.Title>Questions? Contact us.</Footer.Title>
<Footer.Break />
<Footer.Row>
<Footer.Column>
<Footer.Link href="#">FAQ</Footer.Link>
<Footer.Link href="#">Investor Relations</Footer.Link>
<Footer.Link href="#">Privacy</Footer.Link>
<Footer.Link href="#">Speed Test</Footer.Link>
</Footer.Column>
<Footer.Column>
<Footer.Link href="#">Help Center</Footer.Link>
<Footer.Link href="#">Jobs</Footer.Link>
<Footer.Link href="#">Cookie Preferences</Footer.Link>
<Footer.Link href="#">Legal Notices</Footer.Link>
</Footer.Column>
<Footer.Column>
<Footer.Link href="#">Account</Footer.Link>
<Footer.Link href="#">Ways to Watch</Footer.Link>
<Footer.Link href="#">Corporate Information</Footer.Link>
<Footer.Link href="#">Only on Netflix</Footer.Link>
</Footer.Column>
<Footer.Column>
<Footer.Link href="#">Media Center</Footer.Link>
<Footer.Link href="#">Terms of Use</Footer.Link>
<Footer.Link href="#">Contact Us</Footer.Link>
</Footer.Column>
</Footer.Row>
<Footer.Break />
<Footer.Wrapper>
<Footer.SelectWrap>
<Footer.Select defaultValue="/vn-en/">
<Footer.Option value="/vn-en/">English</Footer.Option>
<Footer.Option value="/vn/">Vietnamese</Footer.Option>
</Footer.Select>
</Footer.SelectWrap>
</Footer.Wrapper>
<Footer.Break />
<Footer.Text>Netflix Vietnam</Footer.Text>
</Footer>
);
}
footerContainer.jsx
Okie! Đã xong, chúng ta chạy thử xem thế nào nhé.

Vậy là đã xong phần Footer hehe 😁.
III. Tổng kết.
Okie! bài hôm nay hơi dài, nên mình sẽ tạm kết thúc tại đây, hy vọng bài này sẽ giúp ích cho các bạn thêm về code cũng như làm quen với những tool checking code cũng như convention khi code, giúp chúng ta bớt "gà" hơn, code của chúng ta được gọn gàng và đẹp hơn hehe 😁. Những tool này khi đi làm chắc chắn sẽ có và người ta sẽ bắt rất là kỹ do đó các bạn tập làm quen và tìm hiểu nó nhé 😉. See u again!