Five best practices for React developers

Sep 10, 2020 - 7 min read
Christina Kopecky
editor-page-cover

There are many ways to structure your code so it’s readable, and everyone has their own way to get there. The question becomes, is there a best way to do it?

In this article, I will talk about five of the most common best practices for React developers. React was designed to be customized in just the way you need it to be – it is not opinionated! So, if these five “best practice” scenarios don’t jive with you, it’s totally all right to find your own approach. It’s my hope that you get something out of the way others are currently “Thinking in React”.

The best practices we’ll talk about are:



Take your front-end skills to the next level

Learn how to build highly interactive, professional-quality apps using React.

React for Front-End Developers



Directory Organization

The React Documentation mentions that there are in general two main ways to organize your React Application: Grouping by features or routes and Grouping by file type. The key here is to not overthink.

If starting with a smaller application, you might organically organize your code as you go in a way that works for you. Remember that React is not opinionated, so it’s 100% up to you how things are structured. As long as you have a logical explanation for the way files are organized, it really doesn’t matter too much.

However, since the React Documentation mentions these two organization strategies, let’s take a look at each to see how they are structured. Say we have an e-commerce application that has a user, a list of products, a detailed product page, a shopping cart, and a checkout flow.

How might that be laid out by feature?


Grouping By Features

The root directory here is the src directory, which is one of the two base directories in your React application (the public folder being the other). In the src directory, we’ll have the basic App.js and index.js files in the root of the folder. Then, we’ll have nested directories for each of the features in your application.

Your move may vary as far as how things are organized: it could have more directories, less directories, or even further nesting into components, styling, and testing.

src/
App.js
App.css
App.test.js
index.js
global/ ⇐ items that are in common with entire application
  AppContext.js
  ThemeContext.js
  UserContext.js
  Button.js
cards/
  index.js
  Cards.css
  Cards.js
  Card.css
  Card.js
  Card.test.js
detailed-product/
    DetailedProduct.css
  DetailedProduct.js
  DetailedProduct.test.js
checkout/
  ReviewOrder.css
  ReviewOrder.js
  ReviewOrder.test.js
  ShoppingCart.css
  ShoppingCart.js
  ShoppingCart.test.js  
user/
  index.js
  User.css
  User.js
  User.test.js

Below is one convention. How might the organization look if files were grouped by the type of file it is?


Grouping By File Type

The root directory is still the src directory. Everything the client will see rendered to the screen still goes in this folder. As with before, we will keep the App.js and index.js file in the root of this directory and then have directories representing the constituent parts of the application: components, context, css, hooks and tests.

src/
App.js
index.js
components/
  App.css
  Card.js
  Cards.js
  ConfirmationPage.js
  DetailedProduct.js
  Footer.js
  Navbar.js
  ReviewOrder.js
  Settings.js
  ShoppingCart.js
  User.js
context/ 
  AppContext.js
  ThemeContext.js
  UserContext.js
css/
  Card.css
  Cards.css
  ConfirmationPage.css
  DetailedProduct.css
  Footer.css
  Navbar.css
  ReviewOrder.css
  Settings.css
  ShoppingCart.css
  User.css
hooks/
  useAuth.js
  useAxios.js
  ...other custom hooks
tests/
  App.test.js
  Card.test.js
  Cards.test.js
  ConfirmationPage.test.js
  DetailedProduct.test.js
  Footer.test.js
  Navbar.test.js
  ReviewOrder.test.js
  Settings.test.js
  ShoppingCart.test.js
  User.test.js

As with before, the way your project is set up depends on your application and how you would like to implement it. The basic structure here is dependent on the kind of file it is and nothing more. Ultimately your file structure should be made so it is easy to navigate. How you do that is up to you.


Components and Separation of Concerns

Prior to React Hooks, it was fairly easy to spot what was considered to be a Stateful Class Component vs. a Presentational Functional Component. Some developers have also referred to them as “smart” components vs. “dumb” components. The smart components, of course, are the ones that carry the state and handle logic, and the dumb components are the ones that purely accept props given to them.

After the advent of React Hooks and the update to the Context API, most everything can be considered a functional component, which leads to a conversation about when to separate components containing local state, components that do not, and how to handle that.

Ultimately it’s up to you and/or your team how to construct your design pattern, but best practice tends to keep logic and local stateful components separate from static components.


Handling State and Props

The data flow in a React Application is super important. There are two ways to work with data: using state or passing state down as props. Let’s take a look at best practices.

State

When handling state, whether globally in the context API or locally, it must not be mutated directly by reassigning the property on state with the new value:

 addOne = () => { //Don’t use this!!!!
   this.state.counter += 1;
 }

Instead, when treating state in class components, use the this.setState() method to update state.

import React from "react";
import "./styles.css";
 
class Counter extends React.Component{
 constructor(props) {
   super(props);
   this.state = {
     counter: 0
   }
 }
 
 addOne = () => {
   this.setState({counter: this.state.counter + 1})
 }
 
 subtractOne = () => {
   this.setState({counter: this.state.counter - 1});
 }
 
 reset = () => {
   this.setState({ counter: 0 });
 }
 render() {
   return (
     <div className="App">
       <h1>Simple React Counter</h1>
       <h2>{this.state.counter}</h2>
       <button onClick={this.addOne}> + </button>
       <button onClick={this.reset}> Reset </button>
       <button onClick={this.subtractOne}> - </button>
     </div>
   );
 }
}
 
export default Counter;

When using React Hooks, you’ll use whatever you name your set method:

import React, { useState } from "react";
import "./styles.css";
 
export default function App(){
 
 const [ counter, setCounter ] = useState(0);
 const addOne = () => {
   setCounter(counter + 1)
 }
 
 const subtractOne = () => {
   setCounter(counter - 1);
 }
 
 const reset = () => {
   setCounter(0);
 }
   return (
     <div className="App">
       <h1>Simple React Counter</h1>
       <h2>{counter}</h2>
       <button onClick={subtractOne}> - </button>
       <button onClick={reset}> Reset </button>
       <button onClick={addOne}> + </button>
 
     </div>
   );
}

Props

When dealing with props and passing state down to other components to be used, there may come a point where you need to pass props five children down. This method of passing props down from a parent to child several generations is called props drilling and should be avoided.

While the code would certainly work if you were to pass props down many generations, it’s prone to bugs and the flow of data can be hard to follow. It’s best if you need to give children access to state several generations down, you should create some sort of design pattern for global state management with Redux or Context API (with Context API currently the simpler, more preferred way).

Read more about React design patterns in my article Introducing React Design Patterns: Flux, Redux, and Context API


Abstraction

React thrives on reusability. When talking about React best practices, the term abstraction comes up a lot. Abstraction means that there are portions of a large component or application that can be taken away, made into their own functional component and then imported into the larger component. Making a component as simple as possible, often so it only serves one purpose, increases your chance for the code to be reusable.

In the simple counter application created above, there are opportunities to abstract away some of the elements from the App Component. The buttons can be abstracted into their own component where we pass the method and the button label down as props.

The header and title of the application could also go in their own components. The App Component might look like something like this after we have abstracted everything away:

import React, { useState } from "react";
 
import { Button } from "./Button";
import { Display } from "./Display";
import { Header } from "./Header";
import "./styles.css";
 
export default function App(){
  const addOne = () => {
   setCounter(counter + 1)
 }
 
 const subtractOne = () => {
   setCounter(counter - 1);
 }
 
 const reset = () => {
   setCounter(0);
 }
 
 const initialState = [
   {operation: subtractOne, buttonLabel:"-"},
   {operation: reset, buttonLabel: "reset"},
   {operation: addOne, buttonLabel: "+"}
 ]
 const [ counter, setCounter ] = useState(0);
 const [ buttonContents,  ] = useState(initialState)
    return (
     <div className="App">
       <Header header="Simple React Counter"/>
       <Display counter={counter}/>
       {buttonContents.map(button => {
         return (
           <Button key={button.operation + button.buttonLabel} operation={button.operation} buttonLabel={button.buttonLabel} />
         )
       })}
     </div>
   );
}

The main purpose of abstraction is to make the children components as generic as possible so that they can be reused in whichever way you need. The App Component should only contain all the information that is specific to the application and render or return only smaller components.


Naming Convention

There are three main naming conventions in React that should be considered best practice.

  1. Components should be PascalCase – capitalized in camelCase as well and named for their function and not the specific application feature (in case you change it later).

  2. Elements that need keys should be unique, non-random identifiers (such as individual cards or entries in a CardDeck or List). It is best practice to not use just indexes for keys. It is legal to have a key assignment that is made of a concatenation of two different object properties.

    The basic purpose of the key is to store basic information so that React can get a sense for what has changed in the application.

key={button.operation + button.buttonLabel} 
  1. Methods should be in camelCase and be named for their function and not be application specific. For the same reasons that components are in PascalCase, methods should be named for their purpose and not their feature in the application.

Wrapping up

In this article, we covered five best practices to employ in React development. For many of these cases, it’s more about what works for you and your team as opposed to what is specifically considered “best practice”.

Remember: React doesn’t care how you pass data around - it just cares that you do.

It’s the developers that edit and read your code later that will need to be able to decipher and improve upon your contributions.

I hope this article has helped you with your React journey. Keep learning React and stay updated with all its updates. Check out Educative’s course React for Front-End Developers to build highly interactive, professional-quality apps using React.

Happy learning!


Continue reading about React


WRITTEN BYChristina Kopecky

Join a community of 270,000 monthly readers. A free, bi-monthly email with a roundup of Educative's top articles and coding tips.