Multiple Way To Handle Modal in React

React, React Modal, Coding

Main project image

React has many way to handle a modal or dialog component. Today, I will show you how to handle a modal in react in 3 difference ways. Simple React Component, Modal with React Context, Global Context for All modal component

Simple React Component

A basic way to create modal using pure component with props to control state of dialog. For example we create a modal using MUI Dialog component. Include a modal component in every component that you need to call and use state to control open/close a modal.

const ExamplePage = () => {
	const [open, setOpen] = useState(false)      // Use flag to control open/close
	return <>
	  <Button
	    onClick={() => { setOpen(!open) }}
	    >Open Modal</Button>
	  <Modal 
	    open={open}
	    title="Remove confirmation"
	    message="Are you sure to remove user?"
	    onDismiss={() => { setOpen(false) }}
	    onConfirm={() => { setOpen(false) }}
	    />
	</>
}
const Modal = ({ open, title, message, onConfirm, onDismiss }: ModalProps) => {
  return <Dialog open={open}>
    <DialogHeader title={title} onDismiss={onDismiss} />
    <DialogBody>{message}</DialogBody>
    <DialogFooter>
      <Button color="secondary" onClick={() => { onDismiss() }}>Cancel</Button>
      <Button color="primary" onClick={() => { onConfirm() }}>Confirm</Button>
    </DialogFooter>
  </Dialog>
}

Full example and Demo

Pros

  • It simple react component

Cons

  • Need to include modal component everywhere you need to use. Very inconvenience if using this modal multiple place in apps, such as confirm modal.

If you need to call this modal in multiple place in your apps. Such as Confirm Modal. A previous way isn’t convenient and effectively. Your can use context to create a modal that can open modal and return response from modal in react component without inject a confirm modal in every component.

For example, I need to create confirm modal to call everywhere in app to be confirm before user do an action. Async/Await will be good to use. This goal of this method that I want to only call a function to get a confirm modal open. And then after user click confirm or cancel it will return to a promise to continue program logic.

const isConfirm = await getConfirm({
  title: "Signout Confirmation",
  message: "Are you sure to signout?"
})
if(isConfirm) {
  // When user click on Confirm button
} else {
	// When user click on Cancel button
}

First, we create react context to handle modal.

const ConfirmModalContext = React.createContext({} as ConfirmModalContextProps);

const ConfirmModalProvider = ({ children }: ConfirmModalProviderProps) => {
  const [open, setOpen] = React.useState(false);
  const [config, setConfig] = React.useState({} as ConfirmModalConfig);
  
  const openDialog = (props: ConfirmModalConfig) => {
    setConfig(props);
    setOpen(true);
  };

  const closeDialog = () => {
    setOpen(false);
    setConfig({});
  };

  const onConfirm = () => {
    closeDialog();
    if(config.onConfirm) { config.onConfirm(true) };
  };

  const onDismiss = () => {
    closeDialog();
    if(config.onConfirm) { config.onConfirm(false) };
  };

  return (
    <ConfirmModalContext.Provider value={{ openDialog }}>
      <Modal
        open={open}
        title={config?.title}
        message={config?.message}
        onConfirm={onConfirm}
        onDismiss={onDismiss}
      />
      {children}
    </ConfirmModalContext.Provider>
  );
};

// Promise if you need to use Async/Await to call your modal and then do conditional thing
const useConfirmModal = () => {
  const { openDialog } = React.useContext(ConfirmModalContext);

  const getConfirm = ({ ...options }) =>
    new Promise((res) => {
      openDialog({ onConfirm: res, ...options });
    });

  return { getConfirm };
};

Then wrap your component with provider.

<ConfirmModalProvider>
  <App />
</ConfirmModalProvider>

Then every component inside provider can use a hooks to call modal without include modal component in every place you need to call.

const ExamplePage = () => {
  const { getConfirm } = useConfirmModal()

  const onConfirmModal = async () => {
    const isConfirm = await getConfirm({
      title: "Signout Confirmation",
      message: "Are you sure to signout?"
    })
		// Add result on onConfirm function will return in promise
    if(isConfirm) {
      // Do Signout
    } else {
			// Do nothing
		}
  }
  return <div>
    <Button onClick={onConfirmModal}>Open Modal</Button>
  </div>
}

Full example and Demo

Pros

  • Call modal in react component without include modal component
  • No need to handle open flag or ref to open modal in parent component.

Cons

  • If you have multiple modal to handle this way. It might be a lot of context to wrapper your app component.

Global Modal Context

Similar to react context method. But now we modify to make it accept react component as a parameter to use that component as modal instead of hard code our modal. Now we can use global modal context to handle every react component as dialog.

Similar to React Context, we create react context and hooks to call.

const DialogContext = React.createContext({} as DialogContextProps);

const DialogProvider = ({ children }: DialogProviderProps) => {
  const [open, setOpen] = React.useState(false);
  const [config, setConfig] = React.useState({} as DialogConfig);
  const { dialog: DialogComponent, onDismiss, onConfirm } = config;

  const openDialog = (props: DialogConfig) => {
    setOpen(true);
    setConfig(props);
  };

  const closeDialog = () => {
    setOpen(false);
    if(onDismiss) { onDismiss() }
  };
  
  const confirmDialog = () => {
    setOpen(false);
    if(onConfirm) { onConfirm() }
  };

  return (
    <DialogContext.Provider value={{ openDialog }}>
      { DialogComponent && <DialogComponent
        {...config}
        open={open}
        onDismiss={closeDialog}
        onConfirm={confirmDialog}
       /> }
      {children}
    </DialogContext.Provider>
  );
};

const useDialog = () => {
  const { openDialog } = React.useContext(DialogContext);
  return { openDialog };
};

Then wrap your component with provider.

<DialogProvider>
  <App />
</DialogProvider>

Pass a react component as function parameters

const ExamplePage = () => {
  const { openDialog } = useDialog()

  const onOpenSignInModal = () => {
    openDialog({
      dialog: SignInModal,
      onConfirm: () => { },
      onDismiss: () => { }
    })
  }
  
  const onOpenConfirmModal = () => {
    openDialog({
      dialog: Modal,
      title: "Remove confirmation",
      message: "Are your sure to remove this user?",
      onConfirm: () => { },
      onDismiss: () => { }
    })
  }

  return <div>
    <Button
      onClick={onOpenSignInModal}
    >SignIn Modal</Button>
    <Button
      onClick={onOpenConfirmModal}
    >Confirm Modal</Button>
  </div>
}

Full example and Demo

Pros

  • All modal can be call in app context without include modal component everywhere.
  • Reduce context if your has a multiple modal component to handle with.

Cons

  • A modal component will be mount in react app when you call open function. It might be a little slower than simple react component method.

References