React ⚡️
Formatting
-
-
-
-
Enforce the closing bracket location for JSX multiline elements (learn more)
<Hello lastName="Smith" firstName="John" />;
<Hello firstName="John" lastName="Smith" />; <Hello firstName="John" lastName="Smith" />;
{showButton && <Button /> }
{showButton && ( <Button /> )} {showButton && <Button />}
Naming
-
jsx
extensions for components, screens etc. -
Use
PascalCase
for files with thejsx
extension:components/ ├── Button/ │ ├── index.jsx │ ├── index.test.jsx screens/ ├── Home/ │ ├── index.jsx │ ├── index.test.jsx
-
Use
camelCase
for all other files with ajs
extension:lib/ ├── requestManager.js ├── requestManager.test.js helpers/ ├── userHelper.js ├── userHelper.test.js
-
Use
camelCase
for variables and functions:let form_params = {}; function FetchParams() { return {}; }
let formParams = {}; function fetchParams() { return {}; }
-
Use
PascalCase
for classes:class product_form {} class productForm {}
class ProductForm {}
-
Use
SCREAMING_SNAKE_CASE
for constants:const fetchLimit = 25;
const FETCH_LIMIT = 25;
This formatting is somewhat inherited from Ruby but is widely accepted in the JS community.
-
Use
PascalCase
for components andcamelCase
for their instances:import menuCard from './MenuCard';
import MenuCard from './MenuCard';
const MenuIten = <MenuCard />;
const menuItem = <MenuCard />;
-
Use
camelCase
for prop names andPascalCase
if the prop value is a React component:<Foo UserName='hello' phone_number={ 12345678 } />
<Foo userName='hello' phoneNumber={ 12345678 } Component={ SomeComponent } />
Props
-
Omit the value of the prop when it is explicitly
true
:<Foo hidden={true} />
<Foo hidden />
-
Always include an
alt
prop on<img>
tags. If the image is presentational, alt can be an empty string or the<img>
must haverole='presentation'
attribute:<img src='hello.jpg' />
<img src='hello.jpg' alt='Me waving hello' />
<img src='hello.jpg' alt='' />
<img src='hello.jpg' role='presentation' />
-
Avoid using an array index as key prop, prefer a stable ID:
{ todos.map((todo, index) => <Todo {...todo} key={index} /> ) }
{ todos.map(todo => ( <Todo {...todo} key={todo.id} /> )) }
Tags
-
For single components (having no children), always use self-close tags.
<Foo variant="noChildren"></Foo>
<Foo variant="noChildren" />
-
For components with multi-line properties, close the tag on a new line
<Foo bar="bar" foo="foo" />
<Foo bar="bar" foo="foo" />
Functions
-
Use arrow functions to close over local variables.
function TaskList(props) { return ( <ul> {props.tasks.map((task, index) => ( <Item key={task.key} onClick={() => someFunc(task.name, index)} /> ))} </ul> ); }
-
In the constructor, Bind event handlers for the render method, Otherwise it creates a whole new function on every single render and reduces performance.
class extends React.Component { onClickDiv() { .... } render() { return <div onClick={this.onClickDiv.bind(this)} />; } }
class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { .... } render() { return <div onClick={this.onClickDiv} />; } }
-
Be sure to return a value in
render
methods.render() { (<div />); }
render() { return (<div />); }
Project Structure
After working on several React
projects, we settled down on the following file organization (this structure is closed to the general JS application structure):
adapters/
├── product.js
├── product.test.js
components/
├── Product/
│ ├── index.jsx
│ ├── index.test.jsx
├── ProductList/
│ ├── index.jsx
│ ├── index.test.jsx
constants/
├── product.js
contexts/
├── auth.js
├── auth.test.js
helpers/
├── formatProductDescription.js
├── formatProductDescription.test.js
reducers/
├── product.js
├── product.test.js
screens/
├── ProductDetails/
│ ├── index.jsx
│ ├── index.test.jsx
services/
├── googleMap.js
├── googleMap.test.js
tests/
├── helpers.js
-
adapters/
: the classes in charge of making network and/or async tasks. We usually create one file per API resource. -
components/
: the stateless and reusableReact
components. -
constants/
: thereducers
types under this directory. The organization of this directory should match the folder structure of thereducers
. -
contexts/
: creating contexts and context providers. -
helpers/
: any utilities used in the project. -
reducers/
: theRedux
reducers. The organization of this directory should match the folder structure ofcontexts
andconstants
. -
screens/
: the page-specific components which are in charge of rendering all other stateless components to build any specific page. -
services/
: the service classes used in the project. By definition, these classes should encapsulate one single process of the app. -
tests/
: any tests helpers and generic component wrappers.
Components / Screens
Use a folder structure with an index file (and other files when required):
components/
├── Button.jsx
├── Button.test.jsx
screens/
├── Product.jsx
├── Product.test.jsx
components/
├── Button/
│ ├── index.jsx
│ ├── index.test.jsx
screens/
├── ProductDetails/
│ ├── index.jsx
│ ├── index.test.jsx
This is both a future-proof measure and a mean to break down components into small meaningful modules:
components/
├── Button/
│ ├── index.jsx
│ ├── index.test.jsx
│ ├── loading.jsx
│ ├── loading.test.jsx
│ ├── propTypes.js
screens/
├── ProductDetails/
│ ├── index.jsx
│ ├── index.test.jsx
│ ├── header.jsx
│ ├── header.test.jsx
│ ├── gallery.jsx
│ ├── gallery.test.jsx
Component Props
React PropTypes
are a critical part of creating reusable components with a clear API.
-
Use the right
PropType
that matches the expected data type for each prop:Button.propTypes = { /** * Holds the text to display in the button. **/ text: PropTypes.string, /** * Select the style type. **/ styleType: PropTypes.oneOf(['default', 'primary', 'secondary'] )};
-
Define which
prop
is required:Button.propTypes = { /** * Holds the text to display in the button. **/ text: PropTypes.string.isRequired, /** * Disabled state **/ disabled: PropTypes.bool };
-
Use
PropTypes.shape
to define complex props:/** * Available action creators. **/ actions: PropTypes.shape({ /** * When a ticket is selected. **/ pickTicket: PropTypes.func.isRequired, /** * When a ticket is unselected. **/ unpickTicket: PropTypes.func.isRequired /** * Click event handler. **/ onClick: PropTypes.func, }).isRequired /** * Holds the search store. * */ search: PropTypes.shape({ /** * Holds the origin city. **/ origin: PropTypes.string.isRequired, /** * Holds the destination city. **/ destination: PPropTypes.string.isRequired }).isRequired
The definition of PropTypes
allows for complex type structure between components:
components/
├── Product/
│ ├── index.jsx
│ ├── index.test.jsx
│ ├── propTypes.js
├── Feed/
│ ├── index.jsx
│ ├── index.test.jsx
│ ├── propTypes.js
In Feed/propTypes.js
:
import PropTypes from 'prop-types';
import productPropType from '../Product/propTypes';
export const feedPropType = PropTypes.shape({
products: PropTypes.arrayOf(productPropType).isRequired,
...
});
Reason: Inconsistencies between keyboard shortcuts and keyboard commands used by people using screen readers and keyboards complicate accessibility.
<div accessKey="h" />
<div />
Containers
Use a single file structure:
containers/
├── ProductDetails/
│ ├── index.jsx
│ ├── index.test.jsx
containers/
├── ProductDetails.jsx
├── ProductDetails.test.jsx
Since very little logic should be placed in containers, a single file structure forces to keep the complexity to a minimum.
Hooks
- eslint-plugin-react-hooksESLint plugin for react hook:
This plugin is developed by the React team. It is simple and consists only of a couple of rules
-
This would ensure that Hooks are being called in the same order each time a component renders. That allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls
if (name !== '') { useEffect(function persistForm() { localStorage.setItem('formData', name); } });
useEffect(function persistForm() { if (name !== '') { localStorage.setItem('formData', name); } });
-
For example, the following userInfo component will trigger the exhaustive-deps warning because the userId variable gets referenced inside the useEffect but it is not passed in the dependencies array
function ProductInfo({productId}) { const [product, setProduct] = useState(null) useEffect(() => { getProduct(productId).then(product => seProduct(product)) }, []) // no productId here return <div>Product detail:</div> }
Tests
Test files are located next to the files being tested. This makes code review more natural as the tests changes will be right next to their related code changes.
The name for tests files should follow the {file-name}.test.{extension-name}
convention. For example, an index.jsx
code file would have a test file nammed index.test.jsx
.
The tests
folder contains the needed helpers and/or component wrappers to support tests.
components/
├── Product/
│ ├── index.jsx
│ ├── index.test.jsx
│ ├── product-item.jsx
│ ├── product-item.test.jsx
tests/
├── helpers.js
Many of the React formatting conventions have derived from the Javascript conventions.