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
/tsx
extensions for components, screens etc. and their tests. -
Use
PascalCase
for files with thejsx
/tsx
extensions:components/ ├── Button/ │ ├── index.jsx │ ├── index.test.jsx screens/ ├── Home/ │ ├── index.jsx │ ├── index.test.jsx
-
Use
camelCase
for all other files with ajs
orts
extension:lib/ ├── requestManager.js ├── requestManager.test.js helpers/ ├── userHelper.js ├── userHelper.test.js
-
Use
camelCase
for variables and functions that are not React components:const form_params = {}; const FetchParams = () => { return {}; }
const formParams = {}; const fetchParams = () => { return {}; }
-
Use
PascalCase
for React component functions and classes:class product_form extends React.Component {} class productForm extends React.Component {} const productForm = () => (<></>);
class ProductForm extends React.Component {} const 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'; const dividerComponent = () => (<hr />);
import MenuCard from './MenuCard'; const DividerComponent = () => (<hr />);
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 } ComponentInstance={<h1>Hi</h1>} component={ SomeComponent } />
<Foo userName='hello' phoneNumber={ 12345678 } componentInstance={<h1>Hi</h1>} 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 the
key
prop, prefer a stable ID:{ todos.map((todo, index) => <Todo {...todo} key={index} /> ) }
{ todos.map(todo => ( <Todo {...todo} key={todo.id} /> )) }
Tags
-
For components having no children, always use a self-closed tag.
<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 wrap a function call with local variables.
const TaskList = (props) => ( <ul> {props.tasks.map((task, index) => ( <Item key={task.key} onClick={() => someFunc(task.name, index)} /> ))} </ul> );
Prefer using function components over class components for the following reasons:
-
-
-
-
-
-
The Future Direction: React’s future direction is geared more towards function components. For instance, Concurrent Mode, an experimental feature in React, works only with function components.
class extends React.Component { constructor(props) { super(props); this.onClick = this.onClick.bind(this); } onClick() { // ... } render() { return <div {...props} onClick={this.onClick} />; } }
const MyComponent = (props) => { const onClick = () => { // ... } return <div {...props} onClick={onClick} />; }
Project Structure
After working on several React
projects, the team settled down on the following file organization (this structure is close 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. One file per API resource is usually created. -
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 a future-proof measure that eases breaking 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
When not working with TypeScript, 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 structures 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,
...
});
-
accessKey
attribute on elements (see eslint: jsx-a11y/no-access-key)
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 keeping 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(() { localStorage.setItem('formData', name); }, [name]); };
useEffect(() { if (name !== '') localStorage.setItem('formData', name); }, [name]);
-
useEffect
function should also appear in the dependencies arrayFor 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
const ProductInfo = ({productId}) => { const [product, setProduct] = useState(null) useEffect(() => { getProduct(productId).then(product => seProduct(product)) }, []) // no productId here return <div>Product detail:</div> }
const ProductInfo = ({productId}) => { const [product, setProduct] = useState(null) useEffect(() => { getProduct(productId).then(product => seProduct(product)) }, [productId]) return <div>Product detail:</div> }
Tests
Test files are located next to the files being tested. This makes code review more natural as the test changes will be right next to their related code changes.
The name for test files should follow the {file-name}.test.{extension-name}
convention. For example, an index.jsx
code file would have a test file named 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.