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>
            &nbsp;&nbsp;&nbsp;
            <button type="button" onClick={this.onRemoveLastMessage}>
               Remove last message
            </button>
            &nbsp;&nbsp;&nbsp;
            <button type="button" onClick={this.onClearMessages}>
               Clear messages
            </button>
            <MessageDisplay />
         </div>
      );
   }
}

/* Start the app. */
ReactDOM.render(
   <App />,
   document.getElementById('root')
);

Popular Posts