React promised based Confirm Modal doesn't work properly. Once user cancelled, on subsequent modal openings, even if user confirms, it gets cancelled

Viewed 65

I am using React Final Form for creating forms. I have a component where I have defined the Confirm Modal Box and promise to handle it. Function implementation is like below:

 function Campaign() {
        
      let resolveConfirm;
      let rejectConfirm;
      const confirmPromise = new Promise((resolve, reject) => {
        resolveConfirm = resolve;
        rejectConfirm = reject;
      });
    
      const modal = (
        <ConfirmModal
          name={MODAL_DISABLE_SOCIAL_CAMPAIGNS}
          title={t('Confirm Uncheck')}
          buttonText={t('Confirm')}
          onConfirm={resolveConfirm}
          modalEvents={{
            onClose: () => {
              try {
                rejectConfirm();
              } catch (e) {}
            },
          }}
        >
          <p>{t('Are you sure you want to disable ?')}</p>      
        </ConfirmModal>
      );
     
      return (
        <OpenModal modal={modal}>
          {(open) => (
            <ManageForm        
              openConfirmModal={({ campaign }, form) => {            
                
               const isUnchecked = !campaign?.selected;
               // If unchecked the selected checkbox only then 
               // confirm dialog box should get open

               if (isUnchecked) {
                open();
                return confirmPromise;
               }
    
               return false;

              }}
>
              {(params) => {...
              }}
            </ManageForm>
          )}
        </OpenModal>
    )
    }

The 'ManageForm' Component is imported into the above component 'Campaign' and has a 'save' button which on clicking calls the submission method and confirm modal opens up with options 'cancel' and 'confirm'. Implementation of 'ManageForm' component is like below :

import { Form } from 'react-final-form';
import Button from 'components/UI/Button';

    function ManageForm(){
     const {
      openConfirmModal
     } = props;
    }
    
    // This runs when clicked on a save button.
    const handleSubmit = (formValues, form) => {
    
           if (openConfirmModal) {
            const openPromise = openConfirmModal(formValues, form);
    
            if (openPromise) {
              try {
                await openPromise;
              } catch (e) {
                // Don't continue if the user cancelled the opening
                return;
              }
            }
          }
          
           // Code after this will run only if openPromise is success.
          // Here a network call happens via saveEndpoint method
          const response = await saveEndpoint({ ...formValues });
    }
      return (
        <Form
         onSubmit={handleSubmit}
        >
        (formProps) => {
         return (
         <form>
           ....
           <Button
             label={t('button_label.save')}
             type="submit"
             appearance="brand"
           />
           ....
         </form>
        )}
    </Form>
  );
}

Now, the problem occurs when user after cancelling the confirm modal box for the first time, user clicks on save button again and this time if clicks on confirm it doesn't work. If user when lands on the page first time, and clicks on the save button and then clicks on the confirm button, in that case it works. It's just after cancelling once and then on subsequent confirmation it fails.

My findings :

When user cancels for the first time, rejectConfirm gets called and it sets the confirmPromise as rejected which inside the ManageForm's 'openPromise' if block gives await openPromise value as rejected and code stops. Now when user hits save button again, and this time if user tries to click on confirm, resolvePromise gets called but the value of confirmPromise was already set by rejectConfirm (when user cancelled the first time) and still it remains the same, i.e., rejected. So again await openPromise in ManageForm gives us rejection and confirmation doesn't work.

How can I handle the promise to make this work for further subsequent confirm calls when cancelled in the first time ? I cannot re-render my component whenever user cancels the confirmation, as it will remove all the selected form values.

Thanks.

1 Answers

I get what you are doing. Your promise value ('confirmPromise' here) got set as 'rejected' for the first time you cancelled the confirm modal box. After that I assume rendering of 'Campaign' component doesn't happen and value of confirmPromise remains same. On subsequent confirm modal call, the value of confirmPromise remains same whether resolveConfirm gets called or rejectConfirm gets called. And because of this, in the component 'ManageForm' the await openPromise value is always evaluated as 'rejected' and hence it goes into the catch block and just returns which exits the handleSubmit function too and further lines of action never happen. Simple modelling of what's happening here could be as : jsfiddle.net/utr3Lkp0.

If you check the above fiddle, then inside the ManageForm the promise value always remains as it get set as the first time.

In your case, what I can think of is setting state inside the 'Campaign' component and changing its value when promise get set (cancelled or confirmed). It will cause the re-rendering of the component 'Campaign' and every time a new promise will be there. If you are worrying about the initial values which get lost, you can store them separately somewhere and then can use them to initialise the form after each rendering.

Related