PubSub for communicating between React Components
There are a number of ways for components to communicate with one another in React; each having its pros and cons. We can pass props from parent to child, or use the new Context API to pass information from parents to grandchildren while skipping the children components themselves. We can even use in-memory caching/stores, with the help of higher order components (HOC), to update data and trigger changes in the target components.
However, I recently ran into a situation where my components sometimes needed to share non-persistent information between components. For example: I wanted to have an alert in the main App component of my application and allow any of my other components to publish messages which the App would just, somehow, know how to handle. All of that without adding clutter to my cache or chaining through components to get back to the parent App component.
Enter, the Observer Pattern and Reactive programming.
The Observer Pattern
Back in the ’90s four people: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides wrote a book called: Design Patterns: Elements of Reusable Object-Oriented Software which outlined a number of useful design patterns; one was the Observer Pattern.
Simply stated, the Observer Pattern consists of a single Object called the “Subject”, which maintains a list of Observers. Whenever information is published to the Subject it passes that information to the Observers which then handle the data however they want.
The beauty of the Observer Pattern is that components can publish to the Subject as much as they want and any Observers who care about the information being published will be notified.
Reactive Programming
Now the things is, this story is not about implementing the Observer Pattern. We could write our own Subjects and Observers, but there are already rich, robust frameworks available that have implemented this pattern already. They are known as Reactive frameworks; because they allow the code to react to the notifications. For my PubSub solution I decided to leverage the RxJS framework and write a very simple PubSub component encapsulating the RxJS goodies. That is what the rest of this story is about.
RxJS
To install RxJS in a React based application you can do one of the following:
npm i rxjs
or
yarn add rxjs
Now, RxJS has a lot of tools, but the one we care about for our PubSub implementation is the Subject. In the next section we will create a new PubSub module that allows us to publish to the Subject and allow the Subject to notify its Subscribers.
Let’s get coding…
To kick things off we’ll create PubSub module that imports the needed libraries and sets up the Subject.
import { Subject } from 'rxjs'// The Main Subject/Stream to be listened on.
const mainSubject = new Subject()// This function is used to publish data to the Subject via next().
export const publish = (data) => mainSubject.next(data)
Fairly simple. Now for the fun part: creating a Subscriber component which will leverage the React render props pattern:
import { Component } from 'react'
import { Subject } from 'rxjs'// The Main Subject/Stream to be listened on.
const mainSubject = new Subject()// This function is used to publish data to the Subject via next().
export const publish = (data) => mainSubject.next(data)export class Subscriber extends Component { // Used for unsubscribing when our component unmounts
unsub = null constructor(props) {
super(props)
this.state = { data: null } this.unsub = mainSubject
.subscribe(data => this.setState({ data }))
} componentWillUnmount() {
this.unsub.unsubscribe()
} render() {
return this.props.children(this.state.data)
}
}
The Subscriber Component subscribes to the Subject using a function which sets its state.data to whatever the incoming data is. This will then trigger the render to pass that data to a function in the children of this component. An example of how this would look in practice:
import React from 'react'
import { Subscriber, publish } from './PubSub'export const App = props => {
setTimeout(() => publish('Hello World'), 2000) return (
<div className="App">
<Subscriber>
{text => (<h1>{text}</h1>)}
</Subscriber>
</div>
)
}
Finishing off the code with Filters and Topics
There is still one thing missing. We need topics which components can publish to and Subscribers can listen on. For this we can use a little RxJS magic known as pipes and filters. Pipes are given to us as part of the Subject so all we need to do is import the filter operator and wire it into our code:
import { Component } from 'react'
import { Subject } from 'rxjs'
import { filter } from 'rxjs/operators'// used to export topic constants:
export * from './topics.js'// The Main Subject/Stream to be listened on.
const mainSubject = new Subject()// This function is used to publish data to the Subject via next().
export const publish = (topic, data) => {
mainSubject.next({ topic, data })
}export class Subscriber extends Component {// Used for unsubscribing when our component unmounts
unsub = null constructor(props) {
super(props)
this.state = { topic: props.topic, data: null } this.unsub = mainSubject
.pipe(filter(f => f.topic === this.state.topic))
.subscribe(s => this.setState({ data: s.data }))
} componentWillUnmount() {
this.unsub.unsubscribe()
} render() {
return this.props.children(this.state.data)
}
}
This will now allow us publish and subscribe to Topics. We can create a separate topics.js file to keep our Topic constants in which will be exported through our PubSub component via: export * from './topics.js'
An example topics.js:
export const AlertTopic = 'alert topic'
export const WarningTopic = 'warning topic'
export const InfoTopic = 'info topic'
Example usage
We can see an example of this by having SomeComponent.js publish an alert to the App.js component.
SomeComponent.js:
import React from 'react'
import { publish, AlertTopic } from './PubSub'export const SomeComponent = props => (
<div className="SomeComponent">
<button onClick={() => publish(AlertTopic, 'Hello World')}>
Click Me
</button>
</div>
)
App.js
import React from 'react'
import { Subscriber, AlertTopic } from './PubSub'return (
<div className="App">
<Subscriber topic={AlertTopic}>
{text => (<h1>{text}</h1>)}
</Subscriber>
</div>
)
}
In summary
We covered a lot of ground in this story and many advanced concepts, but I truly hope this helps someone in need of a clean effective solution for inter-component communication without the toil of passing props or dealing with stores such as Redux or MobX.