We create various internal user interface (UI) applications with React inside eBay. Modals, or pop-ups, are widely used to build these complex UI apps. They open a separate window to provide additional information for users, and act similar to a user opening a new browser window — except, with modals, a user can’t leave it until it’s closed.
Last year, we developed a new approach to managing modals at eBay to help streamline workflows and support broader projects. We recently released our new approach to managing modals on open source to share with our tech community.
How We’re Reimagining Modals
In a single page application, we usually use history.push('/some-page') to switch pages. But for modals, we previously didn’t have a convenient way like modal.show/hide to easily manage modals. Under our legacy process, we used a declarative method to manage modals on projects.
Say that we need to create a modal like the below:
The dialog is used to create a JIRA ticket. It could be shown from many places, from the header, to the context menu, to the list page. Traditionally, we had declared modal components with a JSX tag. But then the question became, “Where should we declare the tag?”
The most common option was to declare it wherever it was being used. But using modals in a declarative way is not only about a JSX tag, but also about maintaining the modal’s state like visibility, parameters in the container component. If we look back to the comparison between pages and modals pattern cited here earlier, we found it would be helpful to have a similar API for modals, just like a router manager for pages:
// Router definition for pages
<Route path="/hello-world" component={HelloWorld} />
<Route path="/first-page" component={FirstPage} />
// Modals declaration
<ModalDef id="/jira/create" component={CreateJiraModal} />
<ModalDef id="/user/edit" component={UserEditModal} />
Then, we can use a well designed modal manager to manage these modals:
ModalManager.show('/jira/create');
// or with some props passed to the modal component
ModalManager.show('/user/edit', { userId: 666 });
To hide the modal, we could use ModalManager.hide('/user/edit'). The modal manager provides a high-order component that can handle the transition elegantly when showing/hiding a modal.
Based on this idea, we created a small utility named nice-modal-react to help with managing these items. After using it internally for one year, we have open sourced it at:github.com/eBay/nice-modal-react.
Generally, we use React Context to persist all modals’ state globally, then provide a friendly API to manage the state. For more usage guides, please visit the READEMD.md in the repo. It provides very flexible APIs to address various scenarios.
Next, let’s see the sample usage of this utility.
Creating a Nice Modal
To create a modal, you can use the high order component. Let’s take Ant.Design’s modal component for example:
import { Modal } from 'antd';
import NiceModal, { useModal } from '@ebay/nice-modal-react';
const HelloModal = NiceModal.create(({ name }) => {
// Use the hook to manage modal state
const modal = useModal();
return (
<Modal
title="Hello Antd"
onOk={() => modal.hide()}
onCancel={() => modal.hide()}
afterClose={() => { modal.hideResolve(); modal.remove(); }}
>
Hello ${name}!
</Modal>
);
});
export default HelloModal;
By using create method to create a high order component wrapper of your modal component, it ensures the component code is not executed before visible. And it removes the DOM node after the modal is invisible. Also, the transition of showing/hiding a modal is kept.
The useModal hook returns a modal handler to manage modal state with an intuitive API. You can close the modal in the modal component itself without need to maintain the visibility state out of the component.
Using a Nice Modal
After creating the modal, you can then use it with the NiceModal API. First, wrap your application with NiceModal.Provider:
import NiceModal from '@ebay/nice-modal-react';
ReactDOM.render(
<React.StrictMode>
<NiceModal.Provider>
<App />
</NiceModal.Provider>
</React.StrictMode>,
document.getElementById('root'),
);
Then you can use it with a hook:
import NiceModal, { useModal } from '@ebay/nice-modal-react';
import HelloModal from './HelloModal';
// ...
const modal = useModal(HelloModal);
// Show the modal and pass arguments as props
modal.show({ name: 'Nate' });
// ...
Besides the basic usage, there are very flexible APIs to use the modal. Below are just examples:
// Use modal component with the global method
NiceModal.show(UserInfoModal, { userId: 666 });
// Or using React hook
const modal = NiceModal.useModal(UserEditModal);
// You can also use it by id
const modal = NiceModal.useModal('/user/info/edit')
// Show the modal
modal.show({ userId: 666 });
// After use info updated, refresh the user list
modal.show({ userId: 666 }).then(refreshUserList);
// Wait for the hide transition
await modal.hide();
NOTE: if you use the modal by id, then you need to declare it somewhere or register it:
// Declare the modal directly
<UserInfoModal id="/user/info/edit" />
// or by register api
NiceModal.register('/user/info/edit', UserInfoModal)
// or by ModalDef
<ModalDef id='/user/info/edit' component={UserInfoModal} />
Advantages
Based on this approach, many aspects of the difficulty of using modals in React could be resolved:
- Easy to isolate modals as separate components.
- Uncontrolled. You can close modal itself in the modal component.
- Decoupled. You don’t have to import a modal component to use it. Modals can be managed by id. This is just like managing pages by URL with React Router.
- The code of your modal component is not executed when it’s invisible.
- It doesn’t break the transitions of showing/hiding a modal.
- Promise based. Besides using props to interact with the modal from the parent component, you can do it easier by promise.
- It’s independent so it’s easy to integrate with any UI library.
Summary
In this article we introduced a new approach to managing dialogs/popups/drawers based on the essential of the modal UI pattern. The solution is already open sourced at: github.com/eBay/nice-modal-react. To see live demos, you can also look at: ebay.github.io/nice-modal-react/.