Using PubSubJS in React
Here I demonstrate how to use PubSubJS to allow a React component to listen for changes to some external object (external to the React application).
The problem being solved: I want an array of strings to exist globally such that any Javascript code can modify it (primarily to add to it), while at the same time providing a method for a visual component to listen to state changes on that array and update an on-screen list of those strings.
Main use case I had in mind: helping me to write React code via codepen.io when on my iPad and have no access to Developer Tools, which means I can't see console.log messages.
Helpful resouce: Communication Between Independent Components in React using PubSubJS
PubSubJS API lives at: https://www.npmjs.com/package/pubsub-js.
See my live example of this at codepen.io.
The HTML is extremely basic, just providing a root object for React to hook into.
<div id="root"></div>
The Javascript contains all the real work. I have put comments in-line to make it clear.
import React from 'react';
import ReactDOM from 'react-dom';
import PubSub from 'pubsub-js';
// In codepen.io, import via Settings and not via import statements here
/*
An object that provides controlled access to a
list of strings and notifies subscribers of any changes.
Notifications do not indicate what has changed; only that
something in the list has changed. Subscribers can get a copy
of the whole list as needed.
*/
class MessageList {
/* Start with an empty list. */
constructor() {
this.messages = new Array();
}
/*
Give back a *copy* of the messages so that external
code cannot modify the list themselves.
*/
getMessages() {
return this.messages.slice();
}
/*
Add new message, ignoring attempts to add anything other
than a string.
*/
push(message) {
if (typeof message !== "string") {
return;
}
this.messages.push(message);
PubSub.publish('MESSAGES UPDATED', 'added message');
}
/* Remove last message. */
pop() {
this.messages.pop();
PubSub.publish('MESSAGES UPDATED', 'removed most recent message');
}
/* Remove all messages. */
clear() {
this.messages.length = 0;
PubSub.publish('MESSAGES UPDATED', 'cleared all messages');
}
}
/*
Set up a single instance of a message list that can be used
globally by code wishing to add or remove messages.
*/
window.messages = new MessageList();
/*
This component is only concerned with displaying current
contents of the messages list, updating itself anytime some
change occurs in the list.
*/
class MessageDisplay extends React.Component {
/*
Starting state: no messages.
*/
constructor(props) {
super(props);
this.state = {
messages: [],
};
}
/*
When component is loaded, subscribe to changes in the
messages list.
*/
componentWillMount(){
// Subscribe this class to the 'MESSAGES UPDATED' subscription.
// When a publish event for 'MESSAGES UPDATED' has been fired, MessageDisplay.subscriber() will be triggered.
this.token = PubSub.subscribe('MESSAGES UPDATED', this.subscriber.bind(this));
}
/* Finalise subscription. */
componentDidMount(){
PubSub.publish('MESSAGES UPDATED', this.token);
}
/* Clean up: un-subscribe. */
componentWillUnmount(){
PubSub.unsubscribe(this.token);
}
/* This function is called any time a change to the message list occurs. */
subscriber(msg, data){
console.log("Subscriber has msg " + msg + " and data " + data);
// Update the UI with a new copy of all messages.
this.setState ({
messages : window.messages.getMessages(),
});
}
/* Update UI with a list all current messages. */
render() {
let messagesForDisplay = (this.state.messages ? this.state.messages : new Array());
if (messagesForDisplay.length === 0) {
messagesForDisplay.push("No messages");
}
return (
<div>
<p>Messages:</p>
<ul>
{messagesForDisplay.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
}
/*
Driving app in this case is just a shell to house the
message UI and provide some buttons to allow the user to
update the message list which in turn drives Pub-Sub activity.
*/
class App extends React.Component {
/* Add a message. */
onAddMessage = () => {
window.messages.push("A new message based on date: " + new Date());
}
/* Remove last message. */
onRemoveLastMessage = () => {
window.messages.pop();
}
/* Clear all messages. */
onClearMessages = () => {
window.messages.clear();
}
/* Display buttons and the messsage UI. */
render() {
return (
<div>
<button type="button" onClick={this.onAddMessage}>
Add a message
</button>
<button type="button" onClick={this.onRemoveLastMessage}>
Remove last message
</button>
<button type="button" onClick={this.onClearMessages}>
Clear messages
</button>
<MessageDisplay />
</div>
);
}
}
/* Start the app. */
ReactDOM.render(
<App />,
document.getElementById('root')
);