Startup hacks and engineering miracles from your exhausted friends at Faraday

Confirm dialogs in Redux and React

Nick Husher on

a confirmation box

A fairly common problem in UI design is adding a modal dialog that confirms or cancels a dangerous action. Dan Abramov has a great StackOverflow answer on how to solve this problem for specific kinds of modal dialogs, but not for a generic, reusable modal that can serve many purposes without knowing what those purposes are. In this case, a confirm dialog.

A confirm dialog is made up of at least three parts:

  • Some help text for the user that describes what they are about to do and what any consequences of that action might be.
  • A choice that the user can make that confirms their action
  • A choice that bails out of that action and does nothing

These three parts are mirrored in the Redux action that the Faraday UI dispatches to show a confirm dialog:

showConfirmModal = (body, accept, cancel) => ({  
  type: SHOW_CONFIRM_MODAL,
  payload: { body, accept, cancel }
});

The app's reducer sees this action and stores the new modal state in the redux store; the React UI tree then updates to show the modal dialog. This roughly matches the StackOverflow post linked above.

The big difference here is that the SHOW_CONFIRM_MODAL action takes two Redux actions itself, accept and cancel. When the user makes a choice, that action is dispatched to the app state. This has a two useful implications:

  1. A single confirm dialog can serve many possible purposes: instead of needing to build a new confirm dialog for each case where we might want one, we can reuse the same code for different purposes.

  2. We can easily attach confirm dialogs to any action in the system without building any new special code, or "leaking" the confirm dialog concern throughout our application logic.

Let's look at a concrete example where the confirm dialog is used: removing users from an account. Ideally, we want to write our system logic in a way that's ignorant of the confirm dialog, and we want to write our UI in a way that knows nothing about the details of deletion.

The naïve approach here is to post a KICK_USER action and not build a confirm dialog. Or, of you do build a confirm dialog, to embed the details in a thunk, saga, or whatever other tools you're using to manage asynchrony. This makes the action creator logic very simple, but potentially adds a bunch of complexity elsewhere:

// TODO: Handle confirm dialog
kickUser = user => ({ type: KICK_USER, payload: user.id });  

Using the confirm dialog action creator described above, it becomes trivial to add safety to this destructive action. The action creator for removing users from an account ends up looking like this:

// Show a confirm dialog before deleting the user, natch
confirmKickUser => user => {  
  let body = (
      <p>
        Are you sure you want to remove <br />
        <strong>{user.name}</strong> from this account?
      </p>);
  return showConfirmModal(body, kickUser(user));
};

Just like kickUser described above, we can easily wrap any Flux-style action creator in showConfirmModal and add a safety gate around it.

I hope this has been useful to you, we are certainly going to get a lot of mileage out of it in the Faraday UI codebase.