Search code examples
javascriptreactjsreactjs-fluxflux

ReactJS + Flux - How to implement toasts/notifications?


I'm trying to understand Flux and Reactjs.

Consider a following, very simple scenario:

You have a form with few inputs. When user submits form,

ActionCreator.publishAnnouncement(this.state.announcement);

is called inside my form component. This is how the publishAnnouncement method looks like:

var publishAnnouncement = function (announcement) {
  AnnouncementAPI.publishAnnouncement(
    announcement,
    successCallback,
    failureCallback
  )
};

AnnouncementAPI is just a wrapper upon an AJAX http POST call. It takes two callbacks - on success and on failure.

And now: I need to show a notification/toast on the screen - indicating success or failure. How would you do that in a Flux way?

I was thinking about creating Notification component and rendering it inside my form. Like the following:

<Notification title={this.state.notification.title} message={this.state.notification.title} visible={this.state.notification.visibility}  // ?? onTimeExceeded ??     />

But how do I handle those callbacks? Should I create NotificationStore which listens for ANNOUNCEMENT_PUBLISHING_SUCCEEDED and ANNOUNCEMENT_PUBLISHING_FAILED events? In reaction to those events, store emits CHANGE event and thus my Notification updates.

But even if I do that, how should I instruct my Notification to show/hide? Or worse, to show up and hide after 2 seconds?

I've seen few components on GitHub and each of them uses refs etc, which I personally don't like.

To sum up: How would you implement this? Or maybe such project exists? If so, where can I find it?


Solution

  • I don't see anything wrong with having a store solely for notifications, especially if you want logic around showing/hiding notifications on timers, showing multiple notifications, etc.

    There are two ways I would consider writing this:

    1. Bind the NotificationStore directly to the success/failure callbacks you care about, like you mentioned in your question. Not sure what flux implementation you're using, so this will be pseudocode-y.

      class NotificationStore {
        constructor() {
          this.notificationId = 0;
          this.notifications = {};
          this.bindActionType(
            CLEAR_NOTIFICATION,
            this.handleClearNotification
          );
          this.bindActionType(
            ANNOUNCEMENT_PUBLISHING_SUCCEEDED,
            this.handleAnnouncementPublishingSucceeded
          );
          // etc...
        }
      
        handleAnnouncementPublishingSucceeded(action) {
          this.addNotification("Success!", { timeout: 2000 });
        }
      
        handleClearNotification(action) {
          this.removeNotification(action.notificationId);
        }
      
        addNotification(message, options) {
          const nextId = this.notificationId++;
          const notification = {
            message: message
          };
      
          this.notifications[nextId] = notification;
          this.emit("change");
      
          // if we specified a timeout, remove the notification
          // after the timeout expires.
          if (options.timeout) {
            setTimeout(() => {
              dispatch(CLEAR_NOTIFICATION, {
                notificationId: nextId
              });
            }, options.timeout);
          }
        }
      
        removeNotification(notificationId) {
          delete this.notifications[nextId];
          this.emit("change");
        }
      }
      
    2. Specify the notifications you want in your action creators. This is more explicit but less centralized.

      var publishAnnouncement = function (announcement) {
        AnnouncementAPI.publishAnnouncement(
          announcement,
          (response) => {
            dispatch(ANNOUNCEMENT_PUBLISHING_SUCCEEDED, ...);
            dispatch(CREATE_NOTIFICATION, {
              message: "Success!",
              timeout: 2000
            });
          },
          (error) => {
            dispatch(ANNOUNCEMENT_PUBLISHING_FAILED, ...);
            dispatch(CREATE_NOTIFICATION, {
              message: "Failure!"
            });
          }
        )
      };
      

      In this case, the NotificationStore would look basically the same, but without binding to each and every success/fail action. In either case, I would have a single Notifications widget near the top of my component tree that rendered the list of notifications.