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') );