React is Slow? 6 Strategies for Performance Optimisation
Tags: react, webdev, javascript, programming
Published: 2024-07-08 07:02:44

React excels at managing complex UIs, but its virtual DOM reconciliation process can introduce performance bottlenecks if not optimized effectively. Key factors impacting React performance include:

- **Component Re-renders:** Unnecessary re-renders caused by mutable state or inefficient prop updates can lead to sluggishness.
- **Bundle Size:** Larger bundles take longer to download and parse, impacting initial load times.
- **Third-party Libraries:** While they offer valuable functionality, excessive dependencies can inflate bundle size.
- **Browser Rendering:** Browser's ability to paint and update the DOM can be a limiting factor.

**Performance Optimization Techniques**

**1. Component Optimization**

- **Functional Components:** Prioritize functional components over classes whenever possible. They are simpler, easier to test, and generally perform better.
- **Pure Components (or `React.memo`):** For class-based components, leverage `React.PureComponent` (or the `React.memo` higher-order component) to prevent unnecessary re-renders based on shallow prop and state comparisons.

```javascript
import React, { PureComponent } from 'react';

class MyPureComponent extends PureComponent {
  render() {
    const { name } = this.props;
    return (
      <div>Hello, {name}!</div>
    );
  }
}

// Using React.memo for functional components
const MyMemoizedComponent = React.memo(MyComponent);
```

- **`shouldComponentUpdate` (for Complex State):** For complex state scenarios where `React.PureComponent` is insufficient, implement a custom `shouldComponentUpdate` lifecycle method to control re-renders based on specific state changes.

```javascript
class MyComplexComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return this.props.data !== nextProps.data ||
           !shallowEqual(this.state.items, nextState.items);
  }

  render() {
    // ...
  }
}
```

- **Memoization (`useMemo` and `React.memo`):** Use `useMemo` hook to cache expensive calculations within functional components and `React.memo` to prevent unnecessary re-renders of components based on prop or state changes.

**2. Data Management**

- **Immutable Data Structures:** Consider using libraries like Immer or Immutable.js to manage state immutably. This improves performance by reducing unnecessary re-renders caused by shallow comparisons.
- **Memoized Selectors:** In Redux applications, employ memoized selectors with Reselect to avoid re-computing derived data on every state change.

**3. Component Structure**

- **Smaller Components:** Break down large components into smaller, more manageable ones. This promotes better performance and reusability.

**4. Rendering Optimization**

- **Conditional Rendering:** Conditionally render components based on state or props to avoid rendering unnecessary elements. Utilize ternary operators, logical AND (`&&`), or short-circuiting for concise conditions.

```javascript
const MyComponent = ({ showDetails }) => (
  <div>
    {/* Common content */}
    {showDetails && <DetailsContent />}
  </div>
);
```

- **Fragment Optimization:** Use `React.Fragment` to avoid unnecessary DOM nodes when returning multiple elements from a component that doesn't require a specific wrapper element.

```javascript
const MyComponent = () => (
  <>
    <p>Item 1</p>
    <p>Item 2</p>
  </>
);
```

**5. Virtualization**

- **List Virtualization:** For long lists, implement libraries like React Virtualized, react-window, or Infinite.js to render only visible items, significantly enhancing performance.

**6. Code Splitting and Lazy Loading**

- **Webpack Code Splitting:** Split your application into smaller bundles (code splitting) using Webpack's code splitting techniques. Load only the necessary code on demand using lazy loading (dynamic imports) to reduce initial load times.

```javascript
const OtherComponent = React.lazy(() => import('./OtherComponent'));

const MyComponent = () => (
  <div>
    <button onClick={() => import('./OtherComponent')
      .then(module => {
        const OtherComponent = module.default;
        // Use OtherComponent here
      })}>
      Load Other Component
    </button>
  </div>
);
```

## Using Promise.all for Parallel Requests
Using Promise.all for parallel requests can speed up the process when multiple requests are necessary. However, this isn't a one-size-fits-all solution. Techniques that make operations appear faster can sometimes be more beneficial than genuinely faster ones. It's essential to evaluate the specific needs of your application and choose the most appropriate strategy.

Which ones do you use?
Or any new strategy want to add?