3

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.

Dev9999
  • 51
  • 7
  • 1
    Can you create a working example here? https://codesandbox.io/s/new?file=/src/App.js and share the link in question. It will be easier to follow – Charchit Kapoor Jul 24 '22 at 06:07
  • Hi @CharchitKapoor: Thanks for getting back. I really want to share the working example, but actual code structure has too many parts in it, creating it would be really difficult. Although I would look if I can try to get something in a sandbox. – Dev9999 Jul 24 '22 at 06:13
  • 1
    Don't try to create whole app, just a sample denoting your scenario – Charchit Kapoor Jul 24 '22 at 06:21
  • Modal Mechanism itself has too many parts. Above one is the simplest modelling of the problem we are facing. – Dev9999 Jul 24 '22 at 06:27
  • @Dev9999 Above example is incomplete and can't be run. Just throw your full code into a codesandbox and, before linking here, remove all parts that you find are not needed to reproduce your issue. – inwerpsel Jul 24 '22 at 08:55
  • @inwerpsel Could you please have a look now to the above code. Made some edits. – Dev9999 Jul 24 '22 at 09:18
  • https://codesandbox.io/s/new?file=/src/App.js – inwerpsel Jul 24 '22 at 09:20
  • 1) Go to that site. 2) Put all your code in it. 3) check that it runs and reproduces the issue 4) Remove everything that is not needed to reproduce the issue. 5) Save it and post a link to it here. 6) People can see the problem in action in a few clicks. – inwerpsel Jul 24 '22 at 09:24
  • @inwerpsel Lets' leave it. I cannot do the sandboxing. I was hoping above code snippet would be enough to give an idea. Thanks. – Dev9999 Jul 24 '22 at 09:26
  • "Codesandbox" is as easy to use as your own IDE ([getting started](https://dev.to/codergirl1991/how-to-use-codesandbox-a-beginners-guide-1akl)). It's literally just VS Code in a browser. It's even easier than local development as you don't need to worry about serving pages. If you're building React forms in a modal, you should not have any issue using it. – inwerpsel Jul 24 '22 at 09:31
  • @Dev9999 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 'rejected' and hence it goes into the catch block and just returns which exits the handleSubmit function – user3760959 Jul 24 '22 at 13:51
  • .. too and further lines of action never happen. Simple modelling of what's happening here could be as here : https://jsfiddle.net/utr3Lkp0/ – user3760959 Jul 24 '22 at 14:03
  • If you check the above fiddle, then inside the ManageForm the promise value always remains as it get set as the first time. – user3760959 Jul 24 '22 at 14:05
  • 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. – user3760959 Jul 24 '22 at 14:08

1 Answers1

1

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.

user3760959
  • 457
  • 1
  • 6
  • 18
  • Thanks for reply. My findings too were the same. Could there be a way without setting the state and making the component 'Campaign' re-render. In case of 'confirm' calls, re-rendering happens as after promise evaluated as successful a network call happens to the server for sending the updated form data and then component gets rendered. For 'cancellation' of confirm modal, I don't want re-rendering. Thanks @user3760959 a lot. – Dev9999 Jul 24 '22 at 14:16