Observer pattern | components communication in React - part two

In the first part of this article, I talked about the Pub-Sub pattern — a simple and effective way to share data without strictly following React’s unidirectional data flow. In this part, I’ll talk about another system called the Observer Pattern. Like Pub-Sub, it doesn’t follow React’s top-to-bottom hierarchical data flow — but it approaches the problem in a slightly different way. If this is how the Pub-Sub pattern manages data sharing: Event bus / event channel is where we store the events. The observer pattern is something like this: As you might have noticed from the pics, there are some key differences between the two patterns: In Pub-Sub, the publisher sends the event to an event channel, and the subscriber listens for a specific event name. In the Observer pattern, observers (subscribers) register directly with the subject (publisher) and get notified when its state changes. In the Pub-Sub pattern, the publisher doesn’t know anything about its subscribers, while in the Observer pattern, the subject holds direct references to its observers. About observer Pattern The observer pattern is another way to work around React’s top-down data flow. In this pattern, we’ll talk a lot about two main concepts: Subject and Observers. You can think of the Subject as the Publisher and the Observer as the Subscriber in the Pub-Sub pattern. (While they’re not exactly the same, this comparison might help you better understand their responsibility.) In this pattern, we have a main object called the Subject, which stores and manages a state. It also keeps track of a list of other objects, called Observers, which are notified whenever the state changes. This pattern is another way to work around React's unidirectional data flow. It allows you to update the state from any component and access the updated state wherever it's needed. Enough talking! Let's put it into action and build our Observer utility. What we’ll build: A Subject which holds and update the state Multiple Observers which can subscribe the state updates A custom hook for using this functionality in react Creating a Subject function This function will be responsible for storing and updating the subject’s state. It receives an initialValue, which will be our first state. As before in the Pub-sub pattern, we use generics to make this utility flexible and type-safe. It allows us to define what type of data this subject will manage, so we get full TypeScript support when we want to set or read the state. Storing Observers and value Next we create a variable called value and assign the initValue to it. The reason we do this is that we want to reassign this value over time. Whenever a new state is introduced, we will update this value variable accordingly. In the next line we created another variable to store our observers. As we said, we use observers to listen/subscribe to any state changes. We might have multiple observers in different pages or components so a new Set is a suitable approach for storing this type of data. Using the Set constructor ensures that a function can't be registered multiple times. It also offers faster operations for adding, removing, and checking items _O(1) _ compared to an array. Creating a subscriber function This function takes an observer as its argument, adds it to the list of observers for future updates, and immediately invokes the observer with the current value (which at first was initially set when createSubject was called). At the last lines, we return a callback to clean up the subscribed observer from the observers list. The reason for this is exactly like what we did in our Pub-sub implementation: to avoid memory leaks and prevent multiple updates caused by repeatedly mounting and unmounting components. Updating state We created a function called next. This function receives a new value, replaces the old one, and then updates each observer by calling them with the new value. This way, all observers are notified of the latest update. Both subscribe and next functions call observers, but they serve different responsibilities: subscribe is responsible for the initial setup. It registers observers when they want to start listening to the subject. It’s called once, and immediately notifies the observer with the current state. next updates the internal value with a new state and then notifies all registered observers about this change. So in short: subscribe registers and notifies about the current state, next updates the state and notifies about the new state. Getting the Value In the final step, we create a simple function called get, which returns the current value. We'll use this function to access the latest value at any time, especially when we need to read it or update it with a new one. Finally we expose these three functions for external use. Subscribing to the Observer: Creatin

Apr 26, 2025 - 18:17
 0
Observer pattern | components communication in React - part two

In the first part of this article, I talked about the Pub-Sub pattern — a simple and effective way to share data without strictly following React’s unidirectional data flow.
In this part, I’ll talk about another system called the Observer Pattern. Like Pub-Sub, it doesn’t follow React’s top-to-bottom hierarchical data flow — but it approaches the problem in a slightly different way.

If this is how the Pub-Sub pattern manages data sharing:

pub-sub pattern
Event bus / event channel is where we store the events.

The observer pattern is something like this:

observer pattern

As you might have noticed from the pics, there are some key differences between the two patterns:

  • In Pub-Sub, the publisher sends the event to an event channel, and the subscriber listens for a specific event name.
  • In the Observer pattern, observers (subscribers) register directly with the subject (publisher) and get notified when its state changes.

In the Pub-Sub pattern, the publisher doesn’t know anything about its subscribers, while in the Observer pattern, the subject holds direct references to its observers.

About observer Pattern

The observer pattern is another way to work around React’s top-down data flow.
In this pattern, we’ll talk a lot about two main concepts: Subject and Observers.
You can think of the Subject as the Publisher and the Observer as the Subscriber in the Pub-Sub pattern. (While they’re not exactly the same, this comparison might help you better understand their responsibility.)
In this pattern, we have a main object called the Subject, which stores and manages a state. It also keeps track of a list of other objects, called Observers, which are notified whenever the state changes.

This pattern is another way to work around React's unidirectional data flow. It allows you to update the state from any component and access the updated state wherever it's needed.

Enough talking! Let's put it into action and build our Observer utility.

What we’ll build:

  • A Subject which holds and update the state
  • Multiple Observers which can subscribe the state updates
  • A custom hook for using this functionality in react

Creating a Subject function

creating subject

This function will be responsible for storing and updating the subject’s state. It receives an initialValue, which will be our first state.

As before in the Pub-sub pattern, we use generics to make this utility flexible and type-safe. It allows us to define what type of data this subject will manage, so we get full TypeScript support when we want to set or read the state.

Storing Observers and value

storing value and observers

Next we create a variable called value and assign the initValue to it.

The reason we do this is that we want to reassign this value over time. Whenever a new state is introduced, we will update this value variable accordingly.

In the next line we created another variable to store our observers. As we said, we use observers to listen/subscribe to any state changes.
We might have multiple observers in different pages or components so a new Set is a suitable approach for storing this type of data.

Using the Set constructor ensures that a function can't be registered multiple times. It also offers faster operations for adding, removing, and checking items _O(1) _ compared to an array.

Creating a subscriber function

creating subscriber function

This function takes an observer as its argument, adds it to the list of observers for future updates, and immediately invokes the observer with the current value (which at first was initially set when createSubject was called).

At the last lines, we return a callback to clean up the subscribed observer from the observers list.
The reason for this is exactly like what we did in our Pub-sub implementation: to avoid memory leaks and prevent multiple updates caused by repeatedly mounting and unmounting components.

Updating state

updating state

We created a function called next. This function receives a new value, replaces the old one, and then updates each observer by calling them with the new value. This way, all observers are notified of the latest update.

Both subscribe and next functions call observers, but they serve different responsibilities:

  • subscribe is responsible for the initial setup. It registers observers when they want to start listening to the subject. It’s called once, and immediately notifies the observer with the current state.

  • next updates the internal value with a new state and then notifies all registered observers about this change.

So in short:
subscribe registers and notifies about the current state,
next updates the state and notifies about the new state.

Getting the Value

get function

In the final step, we create a simple function called get, which returns the current value. We'll use this function to access the latest value at any time, especially when we need to read it or update it with a new one.
Finally we expose these three functions for external use.

Subscribing to the Observer: Creating the Custom Hook

This custom hook lets us subscribe to a subject and automatically triggers a re-render whenever the subject’s value changes.

useObserver hook

This hook receives the subject as an argument. It initializes a useState with the subject’s latest value, giving us access to the current state. Then, we use a useEffect hook to subscribe to the subject when the component mounts, this way we ensure the initial value is displayed and updates are tracked.

This Is Where the Magic Happens!
As you might have noticed in the code, we passed our setState function to the subscribe method of our subject. This is a very important step — it's what makes the hook reactive and keeps the component updated.

The setState function is actually acting as our observer. Later on, you'll see how we update the state using the next function, like this:

setting the state

In this example, we call next with the current value (retrieved using get) and increment it by one. Here's what happens inside the next function:

next function

This function updates the internal value and then notifies all registered observers by calling them with the new value. Since our observers are actually setState functions, we’re triggering a React state update and that causes a re-render of any component using this hook.
A similar thing happens inside the subscribe function: it receives the setState function as an argument, adds it to the observers set, and invokes it immediately with the current value to sync the initial state.

At the end we expose the state (which is our latest updated value) for the external usage.

Final Part: Putting everything to use

First, let’s create an instance using our createSubject function. This instance will hold a number and allow us to update it by one. We can create a file called store.ts and add our instances there:

store.ts

Now we can use our instance in any page we want. I used it inside my Home page and call it’s next method to update its initial state by one:

update state

You can click this button as much as you like and still access its latest state in any component or page you need. Pretty incredible, right?
Here’s how I subscribed to the count subject in my Counter page:

subscribing

A Twist For Those Who Aren't Tired Yet!

The Observer pattern is a widely used system for managing data and communication between different parts of an app. Many methods and libraries have taken inspiration from this practical approach.
The useEffect hook is a great demonstration of how this pattern has inspired core concepts in React.
The core idea of the Observer pattern is subscribing to changes and reacting whenever something happens. An observer listens to a subject and gets notified whenever the subject’s state changes. This concept is quite similar to how React’s useEffect hook works.

In React, the useEffect hook allows components to respond to changes in their environment, such as updates to props or state by running side effects when those changes occur.

Also, some web APIs like window.addEventListener are inspired by the Observer pattern.

If we look more deeply, we can say that state management libraries like Redux have also borrowed some ideas from this pattern.

Redux isn't a pure Observer pattern implementation, but it shares some similarities: we could count the store as the subject, components subscribe or listen to the store via selectors, and we can trigger state changes by dispatching actions. When that happens, all the subscribers get notified and can react accordingly.

The Observer Pattern in React: Why It Matters and Where It Fits

As you all know, we have tons of state management libraries for handling component communication. Among them, React’s Context API stands out as a very powerful and straightforward way to manage state and as we said, many of these tools share core ideas with the Observer pattern.

That raises a very valid question — if we already have these libraries and APIs, why the hell would we need to implement our own observer logic to manage data flow?

I don’t want to give you a headache by over explaining this, because honestly—the answer is simple: we don’t need to! We’ve got Context, Redux, and plenty of other libraries ready to use.

But

Choosing the Observer pattern over these tools isn’t about simplicity or being lightweight. It’s about the nature of the data we’re trying to share.

If we’re working with data that needs to be accessed across the entire app, we should lift it up—put it in the top-most layer and treat it as a global state. But if we’re just trying to share some data between two pages or non-parent-child components, the Observer pattern can actually be a smart choice.

Then if we’ve noticed that we’re creating too many observers or subscribing all over the place, it’s time to pause and rethink. The Observer pattern isn’t a great fit when data is used widely in the app. It can make state harder to trace and seriously hurt the developer experience.

If you're interested, you can check out my repo and try using the Observer pattern yourself.
Mohsen KhademHosseini - Mohan