DEV Community

Cover image for Understanding Observer Pattern in React: A Complete Guide πŸ’―
Ali Samir
Ali Samir

Posted on

3

Understanding Observer Pattern in React: A Complete Guide πŸ’―

If you've been building React applications for a while, you've likely encountered moments where components need to stay in sync β€” like when a sidebar needs to update when a user changes a setting in the main content area.

This is where the Observer Pattern can shine. In this guide, we'll break down what the Observer Pattern is, how it works, and how to implement it in React with TypeScript.


πŸš€ What is the Observer Pattern?

The Observer Pattern is a behavioral design pattern where an object (called the Subject) maintains a list of its dependents (called Observers) and notifies them automatically of any state changes. It's a classic publish-subscribe model.

In simpler terms:

When the subject changes, all observers get updated. Think of a YouTube channel (Subject) and its subscribers (Observers). When a new video drops, all subscribers get notified.


🧠 Why Use Observer Pattern in React?

React components often need to communicate. While context, props drilling, or state managers like Redux can help, the Observer Pattern offers a lightweight and decoupled alternative in certain cases:

  • πŸ”„ Sync multiple components without direct connections

  • 🎯 Avoid prop drilling

  • 🧩 Improve separation of concerns

  • ⚑ Great for event-driven architectures


πŸ› οΈ Implementing the Observer Pattern in React with TypeScript

Let’s implement a simple Pub/Sub system using the Observer Pattern.

Step 1: Define the Observer and Subject interfaces

// Observer.ts
export interface Observer {
  update: () => void;
}

export interface Subject {
  attach: (observer: Observer) => void;
  detach: (observer: Observer) => void;
  notify: () => void;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Subject class

// ObservableStore.ts
import { Observer, Subject } from './Observer';

export class ObservableStore implements Subject {
  private observers: Observer[] = [];
  private _state: number = 0;

  attach(observer: Observer): void {
    this.observers.push(observer);
  }

  detach(observer: Observer): void {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify(): void {
    this.observers.forEach(observer => observer.update());
  }

  setState(value: number) {
    this._state = value;
    this.notify();
  }

  get state(): number {
    return this._state;
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a React Component that Observes

// CounterDisplay.tsx
import React, { useEffect, useState } from 'react';
import { Observer } from './Observer';
import { ObservableStore } from './ObservableStore';

interface Props {
  store: ObservableStore;
}

const CounterDisplay: React.FC<Props> = ({ store }) => {
  const [count, setCount] = useState(store.state);

  const observer: Observer = {
    update: () => setCount(store.state)
  };

  useEffect(() => {
    store.attach(observer);
    return () => store.detach(observer);
  }, [store]);

  return <h2>Count: {count}</h2>;
};

export default CounterDisplay;
Enter fullscreen mode Exit fullscreen mode

Step 4: Use it in your App

// App.tsx
import React from 'react';
import { ObservableStore } from './ObservableStore';
import CounterDisplay from './CounterDisplay';

const store = new ObservableStore();

const App: React.FC = () => {
  return (
    <div>
      <h1>Observer Pattern in React</h1>
      <CounterDisplay store={store} />
      <button onClick={() => store.setState(store.state + 1)}>Increment</button>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

βš–οΈ Observer Pattern vs Context API

Feature Observer Pattern Context API
Decoupled components βœ… Yes ❌ No (tightly coupled)
Built-in React support ❌ No βœ… Yes
Performance βœ… Fine-grained updates ❌ Can re-render more than needed
Boilerplate βœ… Simple for small apps βœ… Simple for small data

βœ… When to Use Observer Pattern in React

  • Real-time updates across components

  • External state/event managers (like sockets or websockets)

  • Decoupled, plugin-like architectures

But avoid it if:

  • You already use Redux, Zustand, or Recoil (they often solve the same issue)

  • You’re managing large state trees (can get messy)


🧩 Bonus: Make it Generic πŸ”₯

Want a reusable Observable class that works for any type?

export class ObservableValue<T> implements Subject {
  private observers: Observer[] = [];
  private _value: T;

  constructor(initial: T) {
    this._value = initial;
  }

  attach(observer: Observer): void {
    this.observers.push(observer);
  }

  detach(observer: Observer): void {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify(): void {
    this.observers.forEach(observer => observer.update());
  }

  set value(val: T) {
    this._value = val;
    this.notify();
  }

  get value(): T {
    return this._value;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you can use ObservableValue<string> or ObservableValue<User[]> for maximum flexibility.


πŸ’― Conclusion

The Observer Pattern isn’t new β€” but it’s still super relevant, especially when you want reactive, decoupled communication in your React apps without pulling in heavyweight libraries.

Use it wisely in scenarios where you need modular, responsive updates without tying components directly together.


🌐 Connect With Me On:

πŸ“ LinkedIn
πŸ“ X (Twitter)
πŸ“ Telegram
πŸ“ Instagram

Happy Coding!

Heroku

Amplify your impact where it matters most β€” building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)