promisereact-context

React MUI dialogContext is not resolving promise correctly on close. Data isn't being sent back to component


I am trying to create a dialog context to allow for reusable dialogs. I also am trying to have it return a value onClose to pass that data to the component that needs it.

Here's the relevant portion for the DialogContext:

...
const defaultValue: DialogParams = { open: false, children: null };

export default function DialogProvider({ children }) {
    const [dialog, setDialog] = useState<DialogParams>(defaultValue);
    const [resolver, setResolver] = useState<((value: any) => void) | null>(null);

    const theme = useTheme();
    const fullscreen = useMediaQuery(theme.breakpoints.down('md'));


    function openDialog(options: Omit<DialogParams, "open">): Promise<any> {
        setDialog({ ...options, open: true });
        return new Promise((resolve: (value: any) => void) => {
            setResolver(() => resolve);
        });
    }

    const closeDialog: DialogReturnProps = (props?: any) => {
        if (resolver) {
            resolver(props);
        }
        setResolver(null);
        setDialog(defaultValue);
    }

    const dialogContextObj: DialogState = [openDialog, closeDialog];

    return <DialogContext.Provider value={dialogContextObj}>
        {children}
        {
            <Dialog fullScreen={fullscreen} open={dialog.open} onClose={() => closeDialog()}>
                {
                    <div className="grid"><IconButton className="justify-self-end" onClick={() => closeDialog()}><Close color="error" /></IconButton></div>
                }
                {dialog.children}
            </Dialog>
        }
    </DialogContext.Provider>
}

Here's how I'm currently using it:

const addPlayerDialogRef = useRef(AddPlayerDialog);
const [openDialog, closeDialog] = useDialog();
...
const val = await openDialog({
    children: <AddPlayerDialog handleSubmit={closeDialog} ref={addPlayerDialogRef} />
});
console.log("HERE", val);
if (val) {
   await playerCtx.addPlayers([val]);
   await refresh();
}
...

And the addPlayerDialog:

const AddPlayerDialog = forwardRef<any, { handleSubmit: DialogReturnProps }>(function AddPlayerDialog(props, _) {
    const { handleSubmit } = props;
...
 function addPlayer(ev: FormEvent<HTMLFormElement>) {
        ev.preventDefault();

        const data = new FormData(ev.target as HTMLFormElement)
        if (data.get("firstName") && data.get('lastName') && data.get('gender')) {
            const player: Player = {
                firstName: data.get('firstName')! as string,
                lastName: data.get('lastName')! as string,
                rating: data.get('rating') ? parseInt(data.get('rating')!.toString()) : 0
            };
            handleSubmit(player);
        }
    }

Basically I handle all the validation in the AddPlayerDialog and when it's time to close the dialog, I call the handleSubmit function (which should be the closeDialog function) and that should resolve the promise. However, the issue I'm running into is that half the time it works correctly, and the other half the time the resolver is null. I'm not really sure why it would ever be null especially when we set it in the openDialog function. I can consistently break it when I try to click the "x" button on the dialog to close without sending a value, and then re-open it and try to fill out the form and submit.


Solution

  • The comment from Roar S. fixed the issue I had. I changed from useState to useRef and that allowed it to work. I'm not 100% sure why it wasn't working before, but this does work.

    Here's the change I made:

    Inside the dialog context I changed:

    const [resolver, setResolver] = useState<((value: any) => void) | null>(null);
    

    to

    let resolver = useRef<((value: any) => void) | null>(null);
    

    when calling/updating I did:

    resolver.current(props);
    

    and

       return new Promise((resolve: (value: any) => void) => {
                resolver.current = resolve;
            });