ReactJS Good Practices
Tags: webdev, javascript, react
Published: 2023-11-01 15:03:57

> Written as an exercise for my engineering cohort.

## Introduction

React is a free and open-source front-end JavaScript library for building user interfaces based on [components](#componentization) and [state management](#state-management). It is maintained by Meta and can be used to develop single-page, mobile, or server-rendered applications with [frameworks like NextJS](#frameworks).

ReactJS is a highly flexible framework that offers a wide range of features and capabilities to create highly interactive and dynamic user interfaces. It gives developers the flexibility to choose the best approach for their specific case, allowing for highly customizable and scalable applications. However, with this flexibility comes the responsibility to follow good practices to ensure the code is maintainable, efficient, and optimized.

This document is a record of “good practices”–not “best practices”–as no project utilizing ReactJS has the same requirements or constraints. These are the methods we recommend that will produce good outcomes, but are not strict standards.

At the end of this doc, you’ll also find a list of additional materials–resources, repos, tools, and playgrounds–where you can learn more about ReactJS and play with it.

## Our ReactJS good practices

### Componentization

> **TLDR**: Break your application into smaller, simpler, reusable components.

A component is an independent and reusable piece of code. It serves the same purpose as JavaScript functions, but works in isolation and returns HTML.

Each component should have a specific purpose and should only be responsible for rendering that particular functionality. A component should be reusable, easy to understand, and follow best practices for  [state management](#state-management) and [separation of concerns](#separation-of-concerns).

### Class vs Functional Components

Use functional components as much as possible, as they are simpler, more concise, and easier to test. Use class components when you need to implement lifecycle methods, stateful logic, or when you need to access refs when React Hooks does not support certain functions. For example: `getSnapshotBeforeUpdate`, `getDerivedStateFromError` (such as in [Error Boundaries](#error-boundaries)) and `componentDidCatch` ([source](https://legacy.reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes)).

#### Class Component

Similar to functional component but has some additional features. The functional component does not care about the components in your app whereas the class components can work with each other.

> ✔ Good Example - A class component that extends the Component class and has a render method that returns a simple JSX element.

```javascript
class Welcome extends Component {
  render () {
	return <h1>Hello, {this.props.welcome}</h1>
  }
}
```

> ✖ Bad Example - The below example has a constructor method and binds the handleInputChange method to the component instance, which can cause unnecessary overhead. The component is also not reusable because it has hardcoded logic for setting the name state.

```javascript
class Welcome extends Component {
  constructor(props) {
    super(props);
    this.state = {
      welcome: props.welcome
    };
    this.handleInputChange = this.handleInputChange.bind(this);
  }
  handleInputChange(event) {
    this.setState({ welcome: event.target.value });
  }
  render() {
    return (
      <div>
        <h1>{this.state.welcome}</h1>
        <input type="text"
               value={welcome}
               onChange={handleInputChange} />
      </div>
    )
  }
}
```

#### Functional Component

Functional Components are JavaScript functions. Efficient functional components are used only when the component is not required to interact with any other components or require data from other components. They can inherit variables when called.

We will often recommend functional components over class components, as functional components are simpler and easier to test.

> ✔ Good Example - Takes an object as an argument and returns a simple JSX element; is reusable and easy to understand.

```javascript
function Welcome(props) {
  return <h1>{props.welcome}</h1>
}
```

Testing the above code would only require the test of the component’s single function (in this case, the welcome header).

> ✖ Bad Example - While the below code is equally as valid as the example above, it is more complicated and not single function. In this component, we’re not only dealing with showing the welcome header but also the input where the user can input a new name, the handling of that change, and [state management](#state-management).

> Testing the code below would require not only the header, but also the input, the event handler, and the state change.

> While that might seem manageable, code and applications can get complicated quickly. So for scalable purposes, [keeping the components simple is a necessity](#separation-of-concerns).

```javascript
function Welcome(props) {
  const [welcome, setWelcome] = useState('');
  const handleInputChange = (event) => {
    setName(event.target.value);
  }
  return (
    <div>
      <h1>{welcome}</h1>
      <input type="text"
             value={welcome}
             onChange={handleInputChange} />
    </div>
  )
}
```

### Presentational vs. Container Components

Presentational and container components can be either functional or class components. What separates these two components is their function in the overall application. 

#### Presentational Component

Presentational components are concerned with how things look. It receives data and behavior from parent components.

#### Container Component

Container components are concerned with how things work. They provide the data and behavior to presentational or other container components.

## Function Types

> **General Rules (TLDR)**:
> - [Use **function** in the global scope and for Object.prototype properties.](#named-functions)
> - Use class for object constructors.
> - [Use => everywhere else.](#arrow-functions)

### Arrow Functions

While any arrow function can be replaced by a standard function, there would be little to gain from doing so.

**Why use arrow functions almost everywhere?**

- **Scope safety**: When arrow functions are consistently used, everything is guaranteed to use [the same thisObject as the root](https://www.tutorialspoint.com/what-is-the-arrow-function-in-reactjs). A single standard function in combination with arrow functions can mess up the scope.

- **Code readability**: Arrow functions are easier to read and write. Also, when almost everything is an arrow function, any [regular (named) function](#named-functions) stands out for defining the scope.

> ✔ Good Example - The below is an example of code that is simple and readable. It’s not hard to understand what is happening.

```javascript
var comments = [];
articles.getList()
    .then(articles => Promise.all(
	  articles.map(article => article.comments.getList())))
    .then(commentsLists => commentLists.reduce((a, b) => a.concet(b)));
    .then(comments => {
        this.comments = comments;
    })
}
```

> ✖ Bad Example - The below code works the same as above and for all intents and purposes is perfectly reasonable code. However, there’s a lot of extra characters that make reading the code more difficult.

```javascript
var comments = [];
articles.getList()
    .then(function (articles) {
       return Promise.all(articles.map(function (article) {
          return article.comments.getList();
       }));
    })
    .then(function (commentLists) {
       return commentLists.reduct(function (a, b) {
          return a.concat(b);
       });
    })
    .then(function (comments) {
       this.comments = comments;
    }.bind(this))
```

### Named Functions

**Why always use regular functions on the global scope or module scope?**

- To differentiate the functions that should not access **thisObject**.

- The **window** object (global scope) is best addressed explicitly.

- Many **Object.prototype** definitions live in the global scope (i.e. **String.prototype.truncate**, etc.). These generally have to be named functions and can help avoid errors.

- Function declarations are [hoisted](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function#function_declaration_hoisting) (meaning they can be accessed before they are declared), which is useful in a static utility function.

**Note**: Named functions can double as object constructors.

### Object Constructors

**Note**: If you don’t initialize state and you don’t bind methods, you don’t need to implement a constructor for components.

Typically constructors are used for two purposes:

- Initializing local state by assigning an object to **this.state**.

- Binding event handler methods to an instance.

**Do not call setState()** in the constructor(). Instead, if your component needs to use local state, **assign the initial state to this.state**.

```javascript
constructor(props) {
   super(props);
   this.state = { counter: 0 };
   this.handleClick = this.handleClick.bind(this);
}
```

## State Management

[State](https://react.dev/learn/state-a-components-memory) should be managed in the component where it is needed, and props should be used to pass data and functionality between components.

### Separation of Concerns

> **TLDR**: Separation of concerns is the practice of separating different functionalities of an application into distinct and independent [components](#componentization), each responsible for a specific task.

This separation enables developers to manage and reason about different components independently, reducing the complexity of the application and making it easier to maintain and scale.

### Keep State as Minimal as Possible

> **TLDR**: State should only contain data that is necessary for the component to function.

Avoid using complex state structures to make it easier to manage and debug. There are multiple libraries to help manage complex state management such as [Redux](https://redux.js.org/), [Hookstate](https://hookstate.js.org/), etc.

### React Hooks

[React Hooks](https://react.dev/reference/react) let you use state and other features within functional components. You must import individual hooks from react.

It is possible to [create custom react hooks](https://www.w3schools.com/react/react_customhooks.asp). Custom Hooks start with **use** (i.e. **useFetch**).

**Hook Rules:**
- Hooks can only be called inside React [function components](#functional-component).
- Hooks can only be called at the top level of a component.
- Hooks cannot be conditional.

## Event Handling

React has its own [event system](https://react.dev/learn/responding-to-events). Avoid using native DOM events or directly modifying the state.

Event handlers can pass a data argument using arrow functions. This can help race conditions that can occur.

For example, **useEffect** has a clean up function that we can take advantage of in two ways:

1. If there are going to be several requests, but only want to render the last result, we can use a boolean flag.

```javascript
useEffect(() => {
  let active = true;
  const fetchData = async () => {
     const response = await fetch(...);
     const newData = await response.json();
     if (active) {
        setFetchedId(props.id);
        setData(newData);
     };
  };
  fetchData();
  return () => {
     active = false;
  }
}, [props.id])
```

In the example above:

- Changing **props.id** will cause a re-render.

- Every re-render will trigger the clean up function to run (setting **active** to **false**).

- When **active** is set to **false**, the older requests won’t update the state.

The above example will still have a race condition (multiple requests), but only the results from the newest request will be used.

2. If we don’t have users on Internet Explorer, we can use [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).

```javascript
useEffect(() => {
   const abortController = new AbortController();
   const fetchData = async () => {
      try {
         const response = await fetch(..., {
            signal: abortController.signal
         });
         const newData = await response.json();
         setFetchedId(id);
         setData(newData);
      } catch (error) {
         if (error.name === 'AbortError') {
            // Aborting a fetch throws error
            // Cannot update state
         }
         // Handle other errors
      }
   };
   fetchData();
   return () => {
      return () => {
         abortController.abort();
      }
   }
}, [id])
```

In this example:

- AbortController is initialized at the start of the effect.

- [AbortController.signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal) is passed to **fetch** as an option.

- AbortErrors are caught in the **try**.

- Abort function is called inside the clean-up function.

Unlike the former example, the abort function cancels the in-flight HTTP requests so it will only show the last _successful_ request.

## Type Checking
> **TLDR**: It is recommended to use TypeScript when possible in addition to PropTypes for type checking. TypeScript _should_ catch all errors in compile, but having PropTypes will catch anything that snuck through at runtime.

A robust application should check and confirm that it is receiving and returning the right types of data. One of the weaknesses of JavaScript is how easily variables can change types.

```javascript
// The variable zero can be assigned the integer zero
let zero = 0;
// But then I can reassign it to a string
zero = "zero"
```

Two of the most common ways to check for type checking for React are PropTypes and/or TypeScript. Both can be used separately or together; both differ in how they work.

### propTypes and defaultProps

The PropTypes utility is available through the prop-types package. It is a runtime type-checking tool for props in React Applications. With PropTypes, you can define types of props expected in a component.

However, PropTypes does not provide warnings in production applications. Warnings are only printed in development.

```javascript
import PropTypes from 'prop-types';
const Component = ({ fullName, age, courses, enrolled }) => {
  ...
}
Component.propTypes = {
   fullName: Proptypes.string.isRequired,
   age: Proptypes.int.isRequired,
   courses: Proptypes.array,
   enrolled: Proptypes.bool.isRequired
}
```

### TypeScript

TypeScript is a free and open-source programming language that adds static typing with optional type annotations to JavaScript. Unlike PropTypes, which does type-checking while the application is running in the browser, TypeScript does type-checking during compile time when the TypeScript is compiled in JavaScript. It shows errors in the integrated development environment (IDE) so the problems can be caught earlier.

Here is how the above example could be implemented in TypeScript:

```typescript
import React from 'react';
interface ComponentProps {
  fullName: string;
  age: number;
  courses?: Array<string>;
  enrolled: boolean;
}
const Components: React.FC<ComponentProps> = ({
  fullName,
  age,
  courses,
  enrolled
}) => {
   // ...
}
```

This means that the application will not compile if the code tries to assign a variable that has been assigned one data type (re: a number) another data type (re: a string).

**Note**: Both TypeScript and PropTypes will not catch these issues in production.

## Debugging

### React Developer Tools

> **TLDR**: Browser extension for debugging and inspecting React components. It helps visualize components and their state in real time.

The easiest way to debug websites built with React is to install the [React Developer Tools](https://react.dev/learn/react-developer-tools) browser extension. It is available for several popular browsers:

- [Install for Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
- [Install for Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/)
- [Install for Edge](https://microsoftedge.microsoft.com/addons/detail/react-developer-tools/gpphkfbcpidddadnkolkpfckpihlkkil)

## Testing

**Recommended Testing Frameworks & Utilities:**

- [Jest](https://github.com/facebook/jest)
- [React Testing Library](https://github.com/testing-library/react-testing-library)
- [React Hooks Testing Library](https://github.com/mpeyper/react-hooks-testing-library)

## Tips & Tricks

### Avoid Copying Props to State

**⚠ This is a common mistake:**

```javascript
constructor(props) {
   super(props);
   this.state = { color: props.color };
}
```

The problem is that it’s both unnecessary (you can use **this.props.color** directly) and creates bugs (updates to the color prop won’t be reflected in the state).

### Error Boundaries

> Class components that catch all errors/exceptions thrown at children component levels.

**Note**: ErrorBoundaries should be declared as [class components](#class-component) because they do not support [functional components](#functional-component).

```javascript
// ErrorBoundary Component
class ErrorBondary extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hasErrors: false
    };
  }
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render () {
    if (this.state.hasError) {
      return <ErrorMessage />;
    }
    return this.props.children;
  }
}
```

### Linting

- [EsLint](https://eslint.org/)
- [Prettier](https://prettier.io/)

### React Fragments

Individual components can only return a single element. With Fragment (imported from react), you can group elements together but it does not add any element in the DOM.

```javascript
import { Fragment } from 'react';
const Frag = () => {
  return (
    <Fragment>
      <ComponentA />
      <ComponentB />
    </Fragment>
  )
}
// Or after Babel 7
const NewFrag = () => {
  return (
    <>
      <ComponentA />
      <ComponentB />  
    </>
  )
}
```

## Additional materials

### Official Resources

- [React Documentation](https://reactjs.org/docs)
- [React Github](https://github.com/facebook/react)
- [React Official Website](https://reactjs.org/)
- [ReactJS Tutorial](https://reactjs.org/tutorial/tutorial.html)

### Certifications & Courses

- [Egghead.io](https://egghead.io/q?q=react)
- [Epic React](https://epicreact.dev/)
- [Meta Front-End Developer Professional Certificate](https://www.coursera.org/professional-certificates/meta-front-end-developer)

### Communities

- [Reactiflux Discord Channel](http://www.reactiflux.com/)
- [React SlackOverflow](http://stackoverflow.com/questions/tagged/reactjs)

### Frameworks

- [Blitz](https://blitzjs.com/)
- [GatsbyJS](https://github.com/gatsbyjs/gatsby)
- [NextJS](https://github.com/vercel/next.js)
- [React Admin](https://github.com/marmelab/react-admin)

### Playgrounds

- Interactive:
  - [https://playcode.io/react](https://playcode.io/react)
  - [https://stackblitz.com/edit/react-playground-practice](https://stackblitz.com/edit/react-playground-practice?)

### Resources and Tools

- [Start a New React App ](https://react.dev/learn/start-a-new-react-project)
- [React-Redux Links](https://github.com/markerikson/react-redux-links) - List of tutorials and resources for React/Redux.
- [React Developer Tools](https://react.dev/learn/react-developer-tools) - Inspection of React component hierarchy
- [React Hook Form](https://react-hook-form.com/get-started)
- [React Hot Loader](https://github.com/gaearon/react-hot-loader) - Tweak React components in real time
- [React Loadable](https://github.com/jamiebuilds/react-loadable) - A higher order component for loading components with promises
- [React Router](https://github.com/ReactTraining/react-router)
- [Storybook](https://storybook.js.org/tutorials/intro-to-storybook/react/en/get-started/)
- [tsdx](https://tsdx.io/) - Zero-config CLI for TypeScript package development