React : Design Patterns to Optimize App with Examples Part 1

Design patterns are solutions to common problems that arise when building software applications. In the context of React, several design patterns can be used to optimize the performance and structure of a React app.

Some of the most common design patterns used in React include:

  • Higher-Order Components (HOCs)
  • Render Props
  • Controlled Components
  • State Management Libraries
  • Context API

 

Higher-Order Components (HOCs):

HOCs are functions that take a component and return a new component with added functionality. HOCs can be used to reuse code, manage state and provide extra functionality to components.

Example code for a Higher-Order Component (HOC) design pattern in React:

import React from 'react';

const withAuthentication = (WrappedComponent) => {
  class WithAuthentication extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isAuthenticated: false,
      };
    }

    componentDidMount() {
      // Check authentication status
      // You can replace this with your own authentication logic
      this.setState({ isAuthenticated: true });
    }

    render() {
      return (
        <WrappedComponent
          isAuthenticated={this.state.isAuthenticated}
          {...this.props}
        />
      );
    }
  }

  return WithAuthentication;
};

export default withAuthentication;

 

To use the HOC, you can wrap your component with withAuthentication:

import React from 'react';
import withAuthentication from './withAuthentication';

const MyComponent = (props) => {
  return (
    <div>
      {props.isAuthenticated ? 'You are authenticated' : 'You are not authenticated'}
    </div>
  );
};

export default withAuthentication(MyComponent);

In this example, withAuthentication is a Higher-Order Component that wraps MyComponent and adds authentication functionality to it. The withAuthentication component maintains its own state to keep track of the authentication status, and passes it down to MyComponent as a prop.

 

 

Render Props

Render Props is a pattern that allows components to share code by using a prop as a callback that returns some JSX to be rendered. A render prop is a way for a component to share its state with another component, without having to pass the data down through props. In this pattern, the state-owning component defines a function that returns a component, which is then passed as a prop to another component. The receiving component can then use the data from the state-owning component by calling the render prop function.

Example code for render prop pattern in React:

import React, { useState } from 'react';

// State-owning component
const Counter = ({ render }) => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return render({ count, increment, decrement });
};

// Receiving component
const DisplayCounter = (props) => {
  return (
    <div>
      <p>Count: {props.count}</p>
      <button onClick={props.increment}>+</button>
      <button onClick={props.decrement}>-</button>
    </div>
  );
};

const App = () => {
  return (
    <Counter render={(data) => <DisplayCounter {...data} />} />
  );
};

export default App;

In the above example, Counter component is the state-owning component and DisplayCounter component is the receiving component. The Counter component maintains a count state and provides increment and decrement functions to update the state. The DisplayCounter component receives the data from Counter component through the render prop and displays the count value and buttons to increment and decrement the count.

 

 

Controlled Components

Controlled components are components that receive all of their data and state updates via props. This allows for greater control over components and can be useful when building forms.

Example of using controlled components in React:

import React, { useState } from "react";

function ControlledInput() {
  const [inputValue, setInputValue] = useState("");

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleChange}
      />
      <p>{inputValue}</p>
    </div>
  );
}

export default ControlledInput;

In this example, the input component is a controlled component because the value of the input field is being controlled by the state in the parent component. The handleChange function updates the state with the latest value entered in the input field, and the updated state is then used to set the value of the input field. This makes sure that the input component is always in sync with the state.

 

 

State Management Libraries

State management libraries like Redux and MobX provide centralized state management for React apps, making it easier to manage complex state and improve the overall performance of the app.

Example of using a state management library, such as Redux, in a React application:

  1. First, install the redux and react-redux packages:
npm install redux react-redux

 

  1. Create a Redux store:
import { createStore } from 'redux';

const initialState = {
  count: 0
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

const store = createStore(reducer);

 

  1. Connect the React component to the Redux store:
import React, { useState } from 'react';
import { connect } from 'react-redux';

const Counter = ({ count, increment, decrement }) => {
  return (
    <div>
      <h2>Counter: {count}</h2>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => ({
  increment: () => dispatch({ type: 'INCREMENT' }),
  decrement: () => dispatch({ type: 'DECREMENT' })
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

 

  1. Finally, wrap the React component with the <Provider /> component from react-redux:
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './Counter';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

In this example, we created a simple Redux store that manages the state of a counter, and connected a React component to the store using the connect function from react-redux. By using a state management library like Redux, we can centralize the state of our application, making it easier to manage and reason about.

Context API

The Context API provides a way to share state between components without having to pass props down through multiple components.

Example of how you can use the Context API design pattern in a React app:

import React, { createContext, useState } from "react";

const CounterContext = createContext();

function CounterProvider({ children }) {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={{ count, setCount }}>
      {children}
    </CounterContext.Provider>
  );
}

function Counter() {
  const { count, setCount } = useContext(CounterContext);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  );
}

export default App;

In this example, we’re creating a context called CounterContext using the createContext function from React. Then, we’re creating a CounterProvider component that acts as the provider for the context. The CounterProvider component has a state that keeps track of the count and a setter function to update the count.

Finally, we have a Counter component that consumes the CounterContext using the useContext hook. This component displays the current count and a button to increment it.

The App component wraps the Counter component inside the CounterProvider component, providing the Counter component access to the count and setCount values through the context.

 

These design patterns are just a few examples of the many patterns that can be used to optimize React apps. The best design pattern to use depends on the specific requirements of the app and the developer’s preferred coding style. Also check our Part 2 for other important design patterns we can leverage in React.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top