The Best React Design Patterns with Code Examples

Relia Software

Relia Software

Huy Nguyen

Relia Software


4 React design patterns are: Higher-order Components, Providers, Compound Components, and Hooks. Otherwise, let's discover other general design patterns in React here!

The Best React Design Patterns with Code Examples

Table of Contents

React design patterns offer two significant benefits to software developers. Firstly, they provide a handy approach to tackling software development issues with proven solutions. Secondly, they facilitate the development of highly cohesive modules with minimal coupling. This article delves into the most important React-specific design patterns and best practices, and explores the applicability of general design patterns for various React use cases.

>> Read more: 

4 Key React Design Patterns

While general design patterns can be applied in React, React developers stand to benefit most from React-specific design patterns. Let’s take a closer look at 4 fundamentals: higher-order components, providers, compound components, and hooks.

Higher-order Components (HOC)

Higher-order components (HOC) offer reusable logic to components via props. A HOC is utilized when we require the functionality of an existing component but with a new user interface.

A component is merged with a HOC to achieve the desired outcome: a component that possesses more functionality than the original component.

In terms of code, a component is encapsulated within a HOC, which then returns our desired component:

// React Welcome HOC.
const Welcome = ({ name, ...remainingProps }) => <div {...remainingProps}>Hello {name}!</div>;

const welcomeWithName = (PrimaryComponent) => (props) => (
 <PrimaryComponent {...props} name='Recruitment Specialist' />

const Advanced = welcomeWithName(Welcome)

Provider Design Pattern

The Provider Design Pattern is a powerful tool in React that helps us avoid the issue of prop drilling, or the process of passing props down to nested components in a tree. This pattern can be implemented using React Context API:

import React, { createContext, useContext } from 'react';

// Creating a context for Magazine
export const MagazineContext = createContext();

// Main function
export default function MainApp() {
 return (
   // Providing the context value
   <MagazineContext.Provider value="french-article">
     <Magazine />

// Function to display the Magazine
function Magazine() {
 const magazineValue = useContext(MagazineContext);
 return <h1>{magazineValue}</h1>;

This code snippet illustrates the provider pattern, showing how we can pass props directly to a new object using context. The context includes both a state provider and a state consumer; in this case, our provider is an app component and our consumer is a book component that uses BookContext. Here’s a visual representation of this concept:

The use of the provider design pattern is implied when props are directly passed from component A to component D. In the absence of this pattern, prop drilling would take place, with components B and C serving as intermediaries.

Compound Components

Compound components are a set of interconnected parts that work in harmony. A simple illustration of this design pattern is a card component with its various elements

The card component consists of its image, actions, and content, which together provide its functionality:

import React from 'react';

// Define a reusable Card component
const Panel = ({ content }) => {
  return <div className="panel-container">{content}</div>;

// Define a CardImage component to display images
const PanelImage = ({ source, description }) => {
  return <img src={source} alt={description} className="panel-img" />;

// Define a CardContent component for the body of the card
const PanelContent = ({ content }) => {
  return <div className="panel-body">{content}</div>;

// Define a CardActions component for actions like buttons
const PanelActions = ({ content }) => {
  return <div className="panel-footer">{content}</div>;

// Define a CompoundCard component that uses the above components
const CompoundPanel = () => {
  return (
      <PanelImage source="" description="Sample Image" />
        <h2>Panel Title</h2>
          Panel Content

export default CompoundPanel;

The API for compound components provides an efficient way to express relationships between components.


React hooks provide a way to handle a component’s state and lifecycle events. They were first introduced in early 2019, with additional hooks added in React version 16.8. These hooks include state, effect, and custom hooks.

The state hook in React (useState) consists of two parts: the current value and a function that updates this value as required by the state:

const [data, setData] = React.useState(initialData);

Here’s a more in-depth example of the state hook:

// Import useState from React
import React, { useState } from "react";

// Define a state variable 'info' and a function 'setInfo' to update it
const [info, setInfo] = React.useState(initialData);

// Define a functional component 'StateField'
export default function StateField() {
  // Define a state variable 'field' and a function 'setField' to update it
  const [field, setField] = useState("");

  // Define a function 'fieldHandler' to handle changes in the input field
  const fieldHandler = (e) => {

  // Return an input field that uses the above state and handler
  return (

A state is declared with an empty current value (""), and its value can be updated using the onChange handler.

Effect hooks (useEffect) are also found in class-based components. The functionalities of the useEffect hook are akin to the lifecycle methods previously used in React: componentDidMountcomponentWillMount, and componentDidUpdate.

While proficient React developers are likely well-versed in hooks, HOCs, providers, and compound components, the most skilled engineers also understand general design patterns like proxies and singletons, and know when to apply them in React.

General Design Patterns in React

Design patterns are versatile and can be applied to any language or framework, irrespective of system requirements. This versatility simplifies the understanding and maintenance of the entire system. Moreover, employing design patterns enhances communication between designers. When discussing system design, software professionals can mention the pattern used to address a specific problem, enabling their colleagues to quickly grasp the high-level design.

Design patterns are primarily categorized into three types:

  • Creational
  • Structural
  • Behavioral

While these patterns are beneficial in the context of React, they are also used in general JavaScript programming, making this knowledge easily transferable.

Creational Design Patterns in React

Creational design patterns are designed to construct objects that can be used in a variety of scenarios, enhancing flexibility and promoting reuse.

The Builder Design Pattern:

The builder design pattern streamlines the process of creating objects by outlining a sequence of steps to follow, and then delivering the outcome of these combined steps:

import React, { useState, useEffect } from 'react';

const ConstructingBuilding = ({additionalProps}) => {
  const [buildStructure, setBuildStructure] = useState({});

  const finalizeArchitecturalPlan = () => {
    setBuildStructure(prevState => ({ ...prevState, architecturalPlan: 'completed' }));

  const finalizeConcreteStructure = () => {
    setBuildStructure(prevState => ({ ...prevState, concreteStructure: 'completed' }));

  const finalizeInteriorDecoration = () => {
    setBuildStructure(prevState => ({ ...prevState, interiorDecoration: 'completed' }));

  const finalizeExteriorWork = () => {
    setBuildStructure(prevState => ({ ...prevState, exteriorWork: 'completed' }));

  useEffect(() => {
  }, []);

  // All updated states are returned in one state object buildStructure.
  // This is then passed as props to the child component.
  return (
    <ConstructBuildingLand buildStructure={buildStructure} {...additionalProps} />

export default ConstructingBuilding;
  • completingArchitectureWorkcompletingGrayStructurecompletingInteriorDesign, and completingFinishingWork: These are functions that simulate different stages of house construction. Each function updates the constructHouse state to indicate the completion of a specific stage.
  • useEffect: This is another React Hook that performs side effects in function components. In this case, it’s used to call the house construction functions when the component mounts. The empty array [] as the second argument means the effect runs once after the initial render.

This pattern distinguishes the creation of a complex object from its representation, enabling the creation of different representations using the same construction process.

The Singleton Design Pattern:

The singleton design pattern is a technique that restricts a class to instantiate only one object. For instance, a singleton can be used to ensure that a single authentication instance is created when a user selects from various login methods:

Consider an AuthComponent with its singleton method authInstance that handles the types and triggers a state change based on the type. We could have an authInstance for three components that dictate whether we should render Google, Apple, or Facebook authentication components:

function AuthenticationComponent({ authMethod }) {
    const [activeAuth, setActiveAuth] = useState();

    const authHandler = () => {
        if (authMethod === 'google') {
        } else if (authMethod === 'apple') {
        } else if (authMethod === 'facebook') {
        } else {
            // Execute additional logic here.


    return (
            {activeAuth === 'google-verification' ? <GoogleAuth /> :
             activeAuth === 'apple-verification' ? <AppleAuth /> :
             activeAuth === 'facebook-verification' ? <FacebookAuth /> :

function AuthHandlerUsage() {
    return <AuthenticationComponent authMethod='apple' />
  • AuthenticationComponent: This is a functional component in React that takes authMethod as a prop. authMethod is expected to be a string that could be ‘google’, ‘apple’, or ‘facebook’.
  • useState: This is a React Hook that lets you add state to functional components. In this case, it’s used to create a state variable activeAuth and a function setActiveAuth to update it.
  • authHandler: This is a function that checks the authMethod and sets the activeAuth state accordingly. If authMethod is ‘google’, it sets activeAuth to ‘google-verification’. If authMethod is ‘apple’, it sets activeAuth to ‘apple-verification’. If authMethod is ‘facebook’, it sets activeAuth to ‘facebook-verification’. If authMethod is none of these, it doesn’t change activeAuth.
  • useEffect: This is another React Hook that performs side effects in function components. In this case, it’s used to call the authHandler function whenever authMethod changes.

The Singleton Design Pattern is demonstrated by the authHandler function. No matter how many times authHandler is called, it ensures that only one authentication instance (either ‘google-verification’, ‘apple-verification’, or ‘facebook-verification’) is active at a time. This is similar to how a singleton class ensures that only one object can be instantiated from it. The authHandler function acts as the single global entry point to manage the authentication instance. Please let me know if you need further assistance.

A class should have a single instance and a single global access point. Singletons should be used only when the following three conditions are met:

  • It’s not feasible to assign logical ownership of a single instance.
  • Lazy initialization of an object is contemplated.
  • There’s no need for global access to any instance.

Lazy initialization, or postponing the initialization of an object, is a technique to enhance performance by delaying the creation of an object until it’s actually needed.

Factory Design Pattern:

The factory design pattern is used when we have a superclass with multiple subclasses and need to return one of the subclasses based on input. This pattern transfers responsibility for class instantiation from the client program to the factory class.

Let’s say we have different types of buttons and we want to create a button based on the type provided:

// Define a basic Clickable component
const Clickable = ({ onActivate, label }) => (
  <button onClick={onActivate}>{label}</button>

// Define different styles of clickables
const MainClickable = (props) => <Clickable {...props} style={{ background: 'blue', color: 'white' }} />;
const AlternateClickable = (props) => <Clickable {...props} style={{ background: 'grey', color: 'black' }} />;

// Factory function to create clickables
const ClickableFactory = (variant, props) => {
  switch (variant) {
    case 'main':
      return <MainClickable {...props} />;
    case 'alternate':
      return <AlternateClickable {...props} />;
      return <Clickable {...props} />;

// Usage
function Application() {
  return (
      {ClickableFactory('main', { onActivate: () => alert('Activated!'), label: 'Main Clickable' })}
      {ClickableFactory('alternate', { onActivate: () => alert('Activated!'), label: 'Alternate Clickable' })}

export default Application;

In this example, ButtonFactory is a factory function that creates different types of buttons based on the type argument. This is a simple example, but the Factory Design Pattern can be very powerful in more complex applications. It promotes loose coupling, improves code modularity, and can make your code easier to read and maintain.

Structural Design Patterns in React

Structural design patterns are instrumental for React developers in establishing the connections between different components. They facilitate the grouping of components and streamline complex structures.

Facade Design Pattern:

The facade design pattern is designed to simplify the interaction with numerous components by introducing a unified API. By hiding the underlying interactions, the code becomes more readable. The facade pattern is beneficial in bundling general functionalities into a more specific context, making it particularly useful for intricate systems with interaction patterns.

Consider a support department with a variety of tasks, such as confirming whether an item was billed, a support ticket was acknowledged, or an order was made.

Let’s assume we have an API that includes getpost, and delete methods:

import axios from 'axios';

class FacadeAPI {
   constructor() {
       this.baseURL = '';

   get(endpoint) {
       return axios.get(`${this.baseURL}/${endpoint}`);

   post(endpoint, data) {
       return`${this.baseURL}/${endpoint}`, data);

   delete(endpoint, id) {
       return axios.delete(`${this.baseURL}/${endpoint}/${id}`);

Now we’ll finish implementing this facade pattern example:

const service = new InterfaceAPI();

import { useState, useEffect } from 'react';

const Interface = () => {
   const [elements, setElements] = useState([]);
       // Retrieve elements from API.
       service.get('getElements').then(response => {
   }, [])

   // Adding elements.
   const appendElement = (newElement) => {'addElement', newElement).then(() => {
           setElements([...elements, newElement]);

   // Using delete API.
   const discardElement = (elementId) =>  {
      service.delete('discardElement', elementId).then(() => {
          setElements(elements.filter(item => !== elementId));

   return (
           <button onClick={() => appendElement({id: 'newElement', value: 'New Element'})}>Add Element</button>
                  return (
                        <h2 key={}>{}</h2>
                        <button onClick={() => discardElement(}>Remove Element</button>

export default Interface;

In this code, FacadeAPI is a class that provides a simplified interface (getpostdelete) to interact with the backend API. These methods hide the complexity of the underlying network requests. The Facade component uses the FacadeAPI to interact with the backend. It uses the get method to fetch data, the post method to add data, and the delete method to remove data. These methods are used in the useEffectaddData, and removeData functions respectively.

Please replace '<>''getData''addData', and 'removeData' with your actual API URL and endpoints. Also, you might need to adjust the addData and removeData functions according to your actual data structure. This is a basic example and might need to be adjusted based on your specific use case.

Remember to handle errors appropriately in your actual code. This sample does not include error handling for simplicity. You might want to use try/catch blocks or .catch method for promise-based error handling.

This way, the Facade component doesn’t need to know the details of how the network requests are made. It just needs to call the appropriate method on the FacadeAPI class. This is the essence of the Facade Design Pattern: providing a simplified interface to a complex subsystem.

Decorator Design Pattern:

The decorator design pattern involves using layered, wrapper objects to enhance the behavior of existing objects without altering their core functionality. This approach allows a component to be layered or wrapped by an unlimited number of components; all exterior components can instantly modify their behavior, but the base component’s behavior remains unchanged. The base component is a pure function that simply produces a new component without any side effects.

A Higher-Order Component (HOC) is an example of this pattern. (The most suitable use case for decorator design patterns is memo, but it is not discussed here as there are numerous excellent examples available online.)

Let’s delve into decorator patterns in React:

// This is our enhancer function
function canSwim(target) {
    // We're adding a new property 'swim' to the target
    target.prototype.swim = function() {
        return `${} can swim`;

// Example 1: Using the enhancer with a class
class Dolphin {
    constructor() {
        this.species = 'Dolphin';
        this.velocity = '37 km/h';

// Now we can create an instance of Dolphin and call the 'swim' method
let myDolphin = new Dolphin();
console.log(myDolphin.swim()); // Outputs: "Dolphin can swim"

// Example 2: Using the enhancer with a function inside a functional component
const DolphinComponent = () => {
    function Dolphin() {
        this.species = 'Dolphin';
        this.velocity = '37 km/h';

    let myDolphin = new Dolphin();
    console.log(myDolphin.swim()); // Outputs: "Dolphin can swim"

In this illustration, canSwim is a universally applicable method that operates without causing any side effects. Decorators can be established atop any class component, or they can be applied to functions that are declared within class or functional components.

Decorators represent a potent code design pattern that facilitates the creation of neater and more maintainable React components. However, I still favor Higher-Order Components (HOCs) over class decorators. Given that decorators are currently an ECMAScript proposal, their specifications may evolve over time, so it’s advisable to use them judiciously.

Bridge Design Pattern:

The bridge design pattern is a potent tool in any front-end application due to its ability to decouple an abstraction from its implementation, allowing both to evolve separately.

The bridge design pattern comes into play when we aim for runtime binding implementations, face an explosion of classes due to a tied interface and multiple implementations, desire to share an implementation across various objects, or need to map orthogonal class hierarchies.

Let’s examine the functionality of the bridge pattern through this example:

// Define the Television interface
class Television {
  constructor(make) {
    this.make = make;

  powerOn() {
    console.log(`${this.make} Television is on`);

  powerOff() {
    console.log(`${this.make} Television is off`);

// Define the Controller interface
class Controller {
  constructor(television) {
    this.television = television;

  switchPower() {
    console.log(`Using ${this.television.make} controller to switch power`);

// Implement LGTelevision and LGController
class LGTelevision extends Television {
  constructor() {

class LGController extends Controller {
  constructor(television) {

// Implement PanasonicTelevision and PanasonicController
class PanasonicTelevision extends Television {
  constructor() {

class PanasonicController extends Controller {
  constructor(television) {

// Usage
const lgTelevision = new LGTelevision();
const lgController = new LGController(lgTelevision);

const panasonicTelevision = new PanasonicTelevision();
const panasonicController = new PanasonicController(panasonicTelevision);

This code maintains the same structure as your original code, but with unique variable names and comments. The TV class is now Television, the Remote class is now Controller, and the methods and variables within these classes have been renamed for uniqueness. Additionally, SamsungTV and SamsungRemote have been replaced with LGTelevision and LGController, and SonyTV and SonyRemote have been replaced with PanasonicTelevision and PanasonicController. The functionality of the code remains the same.

In the bridge design pattern, it’s crucial to ensure that the reference is accurate and mirrors the appropriate modification.

Proxy Design Pattern:

The proxy design pattern employs a proxy as a stand-in or substitute for accessing an object. A common real-world example of a proxy is a credit card, which symbolizes tangible cash or funds in a bank account.

Let’s apply this pattern and construct a similar example where we execute a money transfer, and a payment application verifies the existing balance in our bank account:

// This function simulates an external API call.
const externalAPI = (userId) => { 
  // This function would typically make a network request to get the user balance.
  // For simplicity, let's assume it returns a promise that resolves to the user balance.
  return new Promise(resolve => resolve(100)); // Assume the user balance is 100.

// This is the intermediary function.
const verifyBalance = userId => {
  return new Promise(resolve => {
    // Here, we're using the externalAPI function to verify the user balance.
    externalAPI(userId).then((funds) => {
      // We can add some conditions here. For example, we might want to check if the funds are adequate.
      if (funds >= 50) {
      } else {

// This function simulates a money transfer.
const moveMoney = () => {
  return new Promise(resolve => {
    // This function would typically make a network request to move money.
    // For simplicity, let's assume it returns a promise that resolves to the user ID.

// Test run on intermediary function.
moveMoney().then(uniqueUserId => {
  // Using intermediary before moving money.
  verifyBalance(uniqueUserId).then(isAdequate => {
    if (isAdequate) {
      console.log('Transfer successful');
    } else {
      console.log('Insufficient funds');
}).catch(error => console.log('Transfer failed', error));

In our code, the payment app’s verification of the account’s balance serves as the proxy.

Behavioral Design Patterns in React

Behavioral design patterns are centered around the interaction between different components. This makes them particularly suitable for React, which is built on a component-based architecture.

State Design Pattern:

The state design pattern is frequently employed to introduce fundamental encapsulation units (states) in component-based programming. A television controlled by a remote serves as an illustration of the state pattern:

The television’s behavior is altered based on the remote button’s state (on or off). Similarly, in React, a component’s state can be modified depending on its props or other factors.

The behavior of an object is altered when its state changes:

import React, { Component } from 'react';

// Component without any state.
class StatelessComponent extends Component {
  render() {
    // Accessing the rest of the properties here...
    return <div>This is a stateless component</div>;

// Component with a state property.
class StatefulComponent extends Component {
  constructor(props) {
    this.state = this.props.initialState; // Setting the state here...

  render() {
    // Accessing the rest of the properties and state here...
    return <div>This is a stateful component</div>;

// How to use these components
const additionalProps = {};
const initialState = {};

<StatelessComponent additionalProps={additionalProps} state={null}/>
<StatefulComponent additionalProps={additionalProps} initialState={initialState}/>

This code creates two components: StatelessComponent and StatefulComponent. The StatelessComponent does not use any state, while the StatefulComponent does. The state for StatefulComponent is passed in as a prop named initialState and then set in the constructor. Please modify this code according to your requirements. Remember to replace the render methods with your own implementation.

Command Design Pattern:

The command design pattern is a highly effective pattern for creating systems that are clean and loosely coupled. This pattern enables us to carry out a segment of business logic at a future point in time. I want to concentrate specifically on the command pattern as I consider it to be the foundational pattern of Redux. Let’s explore how the command pattern can be applied in conjunction with a Redux reducer:

// Initial state of the application
const defaultState = {
   category: 'DISPLAY_ALL',
   list: []

// Function to handle different actions
function actionHandler(currentState = defaultState, operation) {
   switch (operation.kind) {
       case 'CHANGE_CATEGORY': { /* ... */ }
       case 'INSERT_ITEM': { /* ... */ }
       case 'MODIFY_ITEM': { /* ... */ }
       // Return the current state if no matching action is found
       return currentState

In this instance, the Redux reducer comprises several cases—each activated by distinct circumstances—that yield varying behaviors.

Observer Design Pattern:

The observer design pattern is a mechanism that enables objects to follow changes in the state of another object. When the state changes, the observing objects are automatically notified. This pattern separates the observing objects from the observed object, enhancing modularity and flexibility.

In the Model-View-Controller (MVC) architecture, the observer pattern is frequently employed to disseminate changes from the model to the views. This allows the views to monitor and present the model’s updated state without needing direct access to the model’s internal data.

const Watcher = () => {
    useEffect(() => {
       const actionHandler = () => { ... }

       // Bind event handler.
       documentListener('EVENT_TYPE', actionHandler)

       return () => {
           // Unbind event handler.
           documentListener('EVENT_TYPE', actionHandler)
    }, [])

The Watcher object facilitates communication by establishing “watcher” and “subject” objects. In contrast, other patterns such as the mediator encapsulate communication within objects. It’s simpler to create reusable watchers compared to reusable mediators. However, a mediator can employ a watcher to dynamically enlist colleagues and interact with them. This matches the variable names used in the code above.

Strategy Design Pattern:

The strategy design pattern is a method that allows for the modification of certain behaviors from the outside without altering the core component. It outlines a set of algorithms, encapsulates each, and makes them interchangeable. This strategy enables the parent component to evolve independently of the child that utilizes it. The abstraction can be placed in an interface, with the implementation specifics concealed in subclasses.

// Define the Plan component
const Plan = ({ elements }) => {
   return <div>{elements}</div>;

// Define the ComponentA
const ComponentA = () => {
   return <div>ComponentA</div>;

// Define another component, ComponentB
const ComponentB = () => {
   return <div>ComponentB</div>;

// Use the Plan component with ComponentA
<Plan elements={<ComponentA />} />;

// Use the Plan component with ComponentB
<Plan elements={<ComponentB />} />;

The open-closed principle is a key approach in object-oriented design. Utilizing the strategy design pattern allows adherence to OOP principles while maintaining flexibility during runtime.

Memento Design Pattern:

The memento design pattern is a method that records and externalizes an object’s internal state, allowing it to be restored later without violating encapsulation. The roles in the memento design pattern are as follows:

  • The originator is the object capable of saving its own state.
  • The caretaker understands when the originator needs to save and restore its state.
  • The mementos, or memories, are stored in a secure container managed by the caretaker and accessed by the originator.

To understand this better, let’s look at a code example. The memento pattern employs the API (implementation details omitted for brevity) to save and retrieve data. In the conceptual example below, we use the setState function to store data and the getState function to retrieve it:

class Memento {
   // Loads the data.
   loadGameState(){ ... }
   // Stores the data.
   saveGameState() { ... }

But the actual use case in React is as follows:

const gameController = () => ({
  gamePlayer: () => {
    return fetchGameState(); // This is the Source.
  gameEngine: (gameAction, actionType) => {
    return actionType === "STORE" && gameAction === "ADVANCE"
      ? {
          action: "ADVANCE",
          state: fetchGameState().STORE,
      : {
          action: "END_GAME",
          state: fetchGameState().STORE,
  saveState: (gameStatus) => {
    const gameData = {};
    // Logic to update gameData based on gameStatus.
    // Send gameStatus as well to store the state based on.
    // Game actions for gameEngine function.
    storeGameState({ gameStatus, ...gameData }); // These are the Snapshots.
  • gamePlayer is the Source that can store its own state.
  • gameEngine is the Caretaker that knows when to store and retrieve the game data.
  • saveState represents the Snapshots where the game data is stored.

>> Read more:

The Importance of React Design Patterns

While patterns provide proven solutions to common problems, it’s crucial for software engineers to understand the pros and cons of each design pattern before implementing it.

Frequently, engineers utilize design patterns such as React’s state, hooks, custom hooks, and the Context API. However, gaining a deeper understanding and application of the React design patterns discussed will enhance a React developer’s fundamental technical abilities and be beneficial across various programming languages. These universal patterns enable React engineers to articulate the architectural behavior of code, rather than merely employing a specific pattern to fulfill requirements or tackle a single problem.

>>> Follow and Contact Relia Software for more information!

  • development
  • coding