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;
}
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;
}
}
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;
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 Reacth1>
<CounterDisplay store={store} />
<button onClick={() => store.setState(store.state + 1)}>Incrementbutton>
div>
);
};
export default App;
⚖️ 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;
}
}
Now you can use ObservableValue
or ObservableValue
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!