Our React projects' best practices
This is meant to be a guide to help new developers understand the React code style and best practices we adopt here at Pagar.me.
As this guide is an extension of our JavaScript style guide, we highly recommend reading it before you continue.
The rules described in this repository are also available as a NPM package. To install the package and its dependencies:
$ npm install --save-dev eslint@4.19.1 \
eslint-config-pagarme-react \
stylelint@8.0.0 \
stylelint-config-pagarme-react \
The peer dependencies specified above have hardcoded versions. If you prefer, you can use the command
npm info eslint-config-pagarme-react@latest peerDependencies
to find the exact peer dependencies to install.
To include these rules into your project, create the following config files in your root folder:
.eslintrc
{
"extends": ["pagarme-react"],
"env": {
"browser": true
}
}
.stylelintrc
{
"extends": ["stylelint-config-pagarme-react"]
}
All components (presentation, containers or pages) should always be
defined as a directory, named with pascal casing. The main component file
should be index.js
, main stylesheet style.css
. CSS custom properties
can be kept in properties.css
:
AwesomeCard/
├── index.js
├── properties.css
└── style.css
- Styles should always be defined in a separate CSS file
- Avoid prefixing or suffixing component names
- E.g.:
lib/pages/UserPage
orlib/container/UserContainer
- E.g.:
- On conflict rename on import time
import UserContainer from '...'
-import { User as UserContainer } from '...'
Your project components should be separated in at least three directories:
awesome-react-project/
└── lib/
├── components/
├── containers/
└── pages/
Each of these directories have special types of components:
Stateless components. Shouldn't store state. Most components in this directory will be function-based components. Stuff like buttons, inputs, labels and all presentational components goes here. This components can also accept functions as props and dispatch events, but no state should be held inside.
Container components can store state. Containers are built mostly from the composition of presentational components with some styles to layout them together. Containers can also store internal state and access refs to provide additional logic, but all actions should be accepted as component callbacks.
Page components can store state, receive route parameters and dispatch Redux actions when applicable. Pages are the highest level of application's components. They represent the application routes and most times are displayed by a router. Pages are also responsible for handling container components callbacks and flowing data into children containers.
We use CSS modules everywhere. CSS modules are great because they provide scope to CSS and allow us to create compartmentalized styles that don't leak to global scope. Here are our good practices of doing CSS modules:
Keep your code lines under 80 columns wide. This helps when opening multiple splits. Use soft tabs of 2 spaces to save some space! 😛
With CSS modules, camel case makes much more sense:
GOOD | |
---|---|
lib/components/Input/index.js |
lib/components/Input/style.css |
import style from './style.css'
const Item = ({ children }) =>
<li className={style.circleBullet}>
{children}
</li>
export default Item |
.circleBullet {
list-style-type: disc;
} |
Using ID and tag name at the selector's root makes the rule to be applied globally.
GOOD | |
---|---|
lib/components/Item/index.js |
lib/components/Item/style.css |
import style from './style.css'
const Item = ({ title, thumbnail }) =>
<div className={style.container}>
<img src={thumbnail} alt={title} />
</div>
export default Item |
.container > img {
background-color: #CCCCCC;
} |
BAD | |
lib/components/Item/index.js |
lib/components/Item/style.css |
import style from './style.css'
const Item = ({ title, thumbnail }) =>
<div className={style.container}>
<img src={thumbnail} alt={title} />
</div>
export default Item |
img {
background-color: #CCCCCC;
} |
Organize one selector per line, even when placing all of them at the same line doesn't exceed 80 columns.
GOOD | BAD |
---|---|
.container > img,
.container > div,
.container > section {
background-color: #CCCCCC;
} |
.container > img, .container > div, .container > section {
background-color: #CCCCCC;
} |
Sometimes, not to exceed the 80 columns limit, you need to break lines. While at it, be sure to do it right after the colon, and keep at one argument per line.
GOOD | BAD |
---|---|
.container {
background-color:
linear-gradient(
0deg,
var(--color-light-yellow-12),
var(--color-light-yellow-10),
);
} |
.container {
background-color: linear-gradient(0deg, --color-light...
}
.container {
background-color: linear-gradient(
0deg, var(--color-light-yellow-12), var(--color-lig...
} |
- Put a space before the opening brace
{
- In properties put a space after (but not before) the
:
character - Put closing braces
}
of rule declarations on a new line - Leave ONE blank line in between rule declarations
GOOD | BAD |
---|---|
.container {
font-size: 12pt;
}
.thumbnail {
width: 160px;
height: 90px;
} |
.container{
font-size:12pt;}
.thumbnail{
width:160px;
height:90px;} |
Leaf components shouldn't constrain width or height (unless it makes sense). That said, most components should default to fill the parent:
GOOD | |
---|---|
lib/components/Input/index.js |
lib/components/Input/style.css |
import style from './style.css'
const Input = ({ children }) =>
<input className={style.input}>
{children}
</input>
export default Input |
.input {
box-sizing: border-box;
padding: 10px;
width: 100%;
} |
Sometimes we don't want to fill the whole width by default. An example is the button component, which we want to resize itself based on title width.
In this cases, we should allow the parent component to inject styles into the child component's container. The child is responsible for choosing where parent styles are injected.
For merging styles, always use classnames
package. The
rightmost arguments overrides the leftmost ones.
GOOD | |
---|---|
lib/components/Button/index.js |
lib/components/Button/style.css |
import classNames from 'classnames'
import style from './style.css'
const Button = ({ children, className }) =>
<button className={classNames(style.button, className)}>
{children}
</button>
export default Button |
.button {
box-sizing: border-box;
padding: 10px;
width: 100%;
} |
All components are self-contained and their final size should never suffer margin leakage! This allows the components to be much more reusable!
BAD | GOOD |
---|---|
|
|
When building lists or grids:
- Build list/grid items as separate components
- Use the the list/grid container to space children
- To space them horizontally, use
margin-left
- To space them vertically, use
margin-top
- Select the
first-child
to reset margins
GOOD | |
---|---|
lib/containers/Reviews/index.js |
lib/containers/Reviews/style.css |
import style from './style.css'
const Reviews = ({ items }) =>
<div className={style.container}>
{items.map(item =>
<img src={item.image} alt={item.title} />
)}
</div>
export default Reviews |
.container > img {
margin-left: 10px;
}
.container > img:first-child {
margin-left: unset;
} |
CSS modules already provides us scope. We don't need to use nested classes for providing scope isolation. Use nested class selectors for modifying children based on parent class. A use case is when a component is in error or success state:
BAD | |
---|---|
lib/components/Button/index.js |
lib/components/Button/style.css |
import style from './style.css'
const Button = ({ children }) =>
<button className={style.button}>
<img className={style.icon} />
{children}
</button>
export default Button |
.button {
box-sizing: border-box;
padding: 10px;
width: 100%;
}
.button .icon {
width: 22px;
height: 22px;
} |
GOOD | |
lib/components/Input/index.js |
lib/components/Input/style.css |
import style from './style.css'
const Input = ({ value, onChange, error }) =>
<div className={classNames({ [style.error]: error })}>
<input onChange={onChange} />
<p>{error}</p>
</div>
export default Input |
.error p {
color: red;
display: unset;
} |
We encourage the "variabilification". Always define variables to increase reuse and make styles more consistent. The CSS4 specification defines a way to declare native variables. We adopted them as the standard.
To define a variable accessible globally:
GOOD | |
---|---|
app/App/variables.css |
app/components/Button/styles.css |
:root {
--color-green-1: #6CCFAE;
--color-green-2: #6B66B5;
--color-green-3: #AAC257;
--color-green-4: #68B5C1;
} |
.container {
background-color:
linear-gradient(
0deg,
var(--color-green-1),
var(--color-green-2)
);
} |