2

I try to build a generic confirm component with redux and native promise. I read Dan Abramovs solution here: How can I display a modal dialog in Redux that performs asynchronous actions? but i am looking for a more generic appoach. Basically i want to do this:

confirm({
  type: 'warning',
  title: 'Are you sure?',
  description: 'Would you like to do this action?',
  confirmLabel: 'Yes',
  abortLabel: 'Abort'
})
.then(() => {
  // do something after promise is resolved
})

The confirm method basically opens the modal and returns a promise. Inside the promise i subscribe my redux store, listen for state changes and resolve or reject the promise:

export const confirm = function(settings) {
  // first dispatch openConfirmModal with given props
  store.dispatch(
    openConfirmModal({
      ...settings
    })
  );

  // return a promise that subscribes to redux store
  // see: http://redux.js.org/docs/api/Store.html#subscribe
  // on stateChanges check for resolved/rejected
  // if resolved or rejected:
  // - dispatch closeConfirmModal
  // - resolve or reject the promise
  // - unsubscribe to store
  return new Promise((resolve, reject) => {

    function handleStateChange() {
      let newState = store.getState();

      if (newState.confirmModal.resolved) {
        store.dispatch(closeConfirmModal());
        resolve();
        unsubscribe();
      }

      if (newState.confirmModal.rejected) {
        store.dispatch(closeConfirmModal());
        reject();
        unsubscribe();
      }
    }

    let unsubscribe = store.subscribe(handleStateChange);
  })


}

My confirm component is connected to redux store and is included once in some kind of layout component - so it is useable on all routes in the app:

class ConfirmModal extends Component {

  constructor(props) {
    super(props)
  }

  confirm() {
    this.props.dispatch(resolveConfirmModal());
  }

  abort() {
    this.props.dispatch(rejectConfirmModal());
  }

  render() {
    // my modal window 
  }
}

export default connect(
  state => ({
    confirmModal: state.confirmModal
  })
)(ConfirmModal);

Reducer/Action looks like this:

export const openConfirmModal = (settings) => {
  return {
    type: 'OPEN_CONFIRM_MODAL',
    settings
  };
};

export const resolveConfirmModal = () => {
  return {
    type: 'RESOLVE_CONFIRM_MODAL'
  };
};

export const rejectConfirmModal = () => {
  return {
    type: 'REJECT_CONFIRM_MODAL'
  };
};

export const closeConfirmModal = () => {
  return {
    type: 'CLOSE_CONFIRM_MODAL'
  };
};

const initialState = {
  open: false,
  type: 'info',
  title: 'Are you sure?',
  description: 'Are you sure you want to do this action?',
  confirmLabel: 'Yes',
  abortLabel: 'Abort',
};

export const ConfirmModalReducer = (state = initialState, action) => {
  switch (action.type) {

    case 'OPEN_CONFIRM_MODAL':
      return { ...action.settings, open: true };

    case 'RESOLVE_CONFIRM_MODAL':
      return { ...state, resolved: true };

    case 'REJECT_CONFIRM_MODAL':
      return { ...state, rejected: true };

    case 'CLOSE_CONFIRM_MODAL':
      return initialState;

    default:
      return state;
  }
};

The redux part is working. My confirm window can be open/closed and renders depending on my options. But how i can define a promise in my confirm method that can be resolved in my component? How i get everything connected?

Found a working Solution!

Found a solution that is pretty much what i was looking for:

What do you think?

Community
  • 1
  • 1
d-bro82
  • 490
  • 1
  • 6
  • 21

3 Answers3

1

Well, you can do it, but it won't be pretty. Basically, you need a map of outstanding promises next to your confirm():

var outstandingModals = {}
const confirm = function(settings) {
    return new Promise((resolve, reject) => {
        let id = uuid.v4();
        outstandingModals = resolve;
        store.dispatch(
        openConfirmModal({
            ...settings,
            confirmationId: id,
        })
    );
}

and then later:

case 'CLOSE_CONFIRM_MODAL':
    let resolve = outstandingModals[state.confirmationId];
    if (resolve) {
        resolve();
        delete outstandingModals[state.confirmationId];
    }
    return initialState;

Like I said - ugly. I don't think you can do much better than that using promises.

But you can do better by NOT using promises. What I would do is simply render a Confirm component whenever necessary, say:

render() {
    return <div>
       ... My stuff ...
        {confirmationNecessary && <Confirm text='Are you sure?' onAction={this.thenConfirmed}/>}
       </div>
}

confirmationNecessary can come from this.state or from the store.

Stefan Dragnev
  • 14,143
  • 6
  • 48
  • 52
  • Thank you very much. But with this approach i have to pass my actions to my confirm-component and i have to include the confirm-component in every other component where i want to show a confirm. This is what i am trying to avoid. My Idea was a component that is included once AND don't need to know what to do after confirm. Basically like http://jameskleeh.com/angular-confirm/ – d-bro82 Jan 18 '17 at 16:00
  • That's okay. You could at least use the first option that I suggested - with the `outstandingPromises` map. I wouldn't really use redux for it though. I'd implement something custom, that uses react's `context` property. – Stefan Dragnev Jan 18 '17 at 16:10
1

I wrote a blog post that discusses one possible approach for managing "generic" modals like this: Posts on PacktPub: "Generic Redux Modals" and "Building Better Bundles".

The basic idea is that the code that requested the modal can include a pre-written action as a prop, and the modal can dispatch that action once it's closed.

There's also an interesting-looking library at redux-promising-modals that appears to implement modal result promises through middleware.

Edgar
  • 6,022
  • 8
  • 33
  • 66
markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Thank you, i also read Dan Abramovs approach on StackOverflow. Your Blogpost is very interesting and i like the idea to dispatch with action as payload. I will give it a try but i am missing the promise part :/ – d-bro82 Jan 18 '17 at 19:26
  • Yeah, my approach avoids promises and callbacks in favor of dispatching an action when done. If you really want a promise, you might try that `promising-modals` library. – markerikson Jan 18 '17 at 19:41
0

possible so :)

export const MsgBox = {
 okCancel : s =>
  new Promise((ok, cancel) =>
   confirm(s) ? ok() : cancel() ),

......etc