Design Patterns in React

Introduction

I am a software engineer. I love coding, and I know how to code—I’m actually quite good at it. I know how to code and I use most design patterns automatically, but I honestly do not know what they are called (with a few exceptions).

Here are some common design patterns explained with React code:

Singleton Pattern

Ensures a class has only one instance.

Example in React: A global state management instance (e.g., Redux store, Context API provider).

const StoreSingleton = (function () {
  let instance;
  function createInstance() {
    return { user: null, theme: "dark" };
  }
  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    },
  };
})();

const store = StoreSingleton.getInstance();

Factory Pattern

Creates objects without specifying the class.

Example in React: Dynamic component rendering based on props.

const ButtonFactory = ({ type, label }) => {
  const components = {
    primary: (label) => <button className="btn btn-primary">{label}</button>,
    secondary: (label) => <button className="btn btn-secondary">{label}</button>,
  };
  return components[type] ? components[type](label) : <button>{label}</button>;
};

// Usage
<ButtonFactory type="primary" label="Click Me" />;

Observer Pattern

Notifies dependent objects when one changes.

Example in React: Event listeners, pub-sub mechanism, React Context API.

import { useState, useEffect } from "react";

const eventListeners = new Set();

const subscribe = (listener) => eventListeners.add(listener);
const unsubscribe = (listener) => eventListeners.delete(listener);
const notifyAll = (data) => eventListeners.forEach((listener) => listener(data));

const useNotification = () => {
  const [message, setMessage] = useState(null);
  useEffect(() => {
    const listener = (data) => setMessage(data);
    subscribe(listener);
    return () => unsubscribe(listener);
  }, []);
  return message;
};

// Usage
const NotificationDisplay = () => {
  const message = useNotification();
  return message ? <p>{message}</p> : null;
};

Decorator Pattern

Dynamically adds features without modifying core code.

Example in React: Higher-Order Components (HOCs) & Custom Hooks.

const withLogging = (WrappedComponent) => {
  return (props) => {
    console.log("Component Rendered:", WrappedComponent.name);
    return <WrappedComponent {...props} />;
  };
};

const Button = (props) => <button {...props}>{props.label}</button>;

const LoggedButton = withLogging(Button);

// Usage
<LoggedButton label="Click Me" />;

Adapter Pattern

Converts incompatible interfaces for seamless interaction.

Example in React: Converting an API response to match UI needs.

const legacyAPIResponse = { fname: "John", lname: "Doe" };

const adaptUser = (data) => ({
  firstName: data.fname,
  lastName: data.lname,
});

const UserProfile = ({ user }) => {
  const adaptedUser = adaptUser(user);
  return <p>{adaptedUser.firstName} {adaptedUser.lastName}</p>;
};

// Usage
<UserProfile user={legacyAPIResponse} />;

Strategy Pattern

Encapsulates algorithms for flexible swapping.

Example in React: Using different sorting algorithms in a table component.

const strategies = {
  ascending: (arr) => [...arr].sort(),
  descending: (arr) => [...arr].sort().reverse(),
};

const SortableList = ({ items, strategy }) => {
  const sortedItems = strategies[strategy](items);
  return <ul>{sortedItems.map((item) => <li key={item}>{item}</li>)}</ul>;
};

// Usage
<SortableList items={["Banana", "Apple", "Cherry"]} strategy="ascending" />;

Command Pattern

Wraps requests as objects to allow undo/redo.

Example in React: Undo/redo for a text editor.

import { useState } from "react";

const useCommand = () => {
  const [history, setHistory] = useState([]);
  const [index, setIndex] = useState(-1);

  const execute = (command) => {
    const newHistory = history.slice(0, index + 1);
    newHistory.push(command);
    setHistory(newHistory);
    setIndex(newHistory.length - 1);
    return command.execute();
  };

  const undo = () => {
    if (index >= 0) {
      const command = history[index];
      setIndex(index - 1);
      return command.undo();
    }
  };

  return { execute, undo };
};

Composite Pattern

Treats objects and compositions the same way.

Example in React: Recursive component rendering.

const Comment = ({ text, replies }) => (
  <div>
    <p>{text}</p>
    {replies && replies.map((reply, i) => <Comment key={i} {...reply} />)}
  </div>
);

Conclusion

I use most of these design patterns daily—but I don’t know what they are called. I know I don’t like a services pattern (oh sorry, the Facade pattern) as I like keeping all my fetching code together in my Context. Though, to be honest, I am starting to wonder why.

Why Are We Still Using CVs in 2025? AI Can Do Better

In a world where AI can write essays, generate code, and even hold conversations, why am I still manually sending my CV to apply for jobs? The traditional recruitment process—where a job is posted, recruiters screen candidates, and job seekers send applications—feels outdated and inefficient.

Let’s be honest: companies already struggle with an overwhelming number of applications, and job seekers waste hours tweaking resumes and searching for roles that match their skills. But what if AI could eliminate this manual back-and-forth entirely?

The AI-Powered Hiring Solution

Imagine a platform where companies upload a job description, and AI automatically extracts key requirements—skills, experience, and qualifications. On the other side, candidates upload their CVs, and AI instantly matches them to relevant job postings. Instead of job seekers manually searching through thousands of ads, AI alerts recruiters when a highly matched candidate appears.

Why This is Better for Everyone

For Job Seekers: No more hours spent on job boards. You simply upload your details, and AI ensures your profile gets seen by the right employers.

For Recruiters: Instead of filtering through endless applications, AI presents only the most relevant candidates, speeding up hiring decisions.

For Companies: A streamlined, data-driven hiring process that reduces hiring costs and ensures better role-fit candidates.

Is This a New SaaS Opportunity?

The more I think about it, the more it feels like AI is ready to disrupt the hiring process entirely. Instead of companies and candidates spending countless hours searching for each other, why not let AI handle the matchmaking?

This could be built as a SaaS platform that integrates with job boards and applicant tracking systems, making hiring faster, smarter, and more efficient.

The question is—who’s already working on this? And if no one has done it right, maybe it’s time to build it.