Convert Figma logo to code with AI

eBay logonice-modal-react

A modal state manager for React.

2,195
124
2,195
31

Top Related Projects

34,369

Build forms in React, without the tears 😭

Accessible modal dialog component for React

Bootstrap components built with React

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.

An enterprise-class UI design language and React UI library

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.

Quick Overview

Nice Modal React is a lightweight library for managing modals in React applications. It provides a simple and flexible API for creating, showing, and hiding modals, with support for nested modals and custom modal components.

Pros

  • Easy to use and integrate into existing React projects
  • Supports nested modals and custom modal components
  • Lightweight with minimal dependencies
  • Provides a clean and declarative API for modal management

Cons

  • Limited built-in styling options, requiring custom CSS for advanced designs
  • May require additional setup for server-side rendering
  • Documentation could be more comprehensive for advanced use cases
  • Limited community support compared to more established modal libraries

Code Examples

Creating a basic modal:

import React from 'react';
import { create, useModal } from '@ebay/nice-modal-react';

const MyModal = create(({ name }) => {
  const modal = useModal();
  return (
    <div className="modal">
      <h1>Hello, {name}!</h1>
      <button onClick={() => modal.hide()}>Close</button>
    </div>
  );
});

export default MyModal;

Showing a modal from a component:

import React from 'react';
import { useModal } from '@ebay/nice-modal-react';
import MyModal from './MyModal';

const App = () => {
  const modal = useModal(MyModal);

  return (
    <div>
      <button onClick={() => modal.show({ name: 'World' })}>Open Modal</button>
    </div>
  );
};

Using nested modals:

import React from 'react';
import { create, useModal } from '@ebay/nice-modal-react';

const NestedModal = create(() => {
  const modal = useModal();
  return (
    <div className="modal">
      <h2>Nested Modal</h2>
      <button onClick={() => modal.hide()}>Close</button>
    </div>
  );
});

const ParentModal = create(() => {
  const modal = useModal();
  const nestedModal = useModal(NestedModal);

  return (
    <div className="modal">
      <h1>Parent Modal</h1>
      <button onClick={() => nestedModal.show()}>Open Nested Modal</button>
      <button onClick={() => modal.hide()}>Close</button>
    </div>
  );
});

Getting Started

  1. Install the package:

    npm install @ebay/nice-modal-react
    
  2. Wrap your app with NiceModal.Provider:

    import React from 'react';
    import NiceModal from '@ebay/nice-modal-react';
    
    const App = () => (
      <NiceModal.Provider>
        {/* Your app components */}
      </NiceModal.Provider>
    );
    
  3. Create and use modals as shown in the code examples above.

Competitor Comparisons

34,369

Build forms in React, without the tears 😭

Pros of Formik

  • More comprehensive form management solution, handling form state, validation, and submission
  • Larger community and ecosystem, with extensive documentation and third-party integrations
  • Reduces boilerplate code for complex form scenarios

Cons of Formik

  • Steeper learning curve due to its extensive API and concepts
  • Can be overkill for simple form implementations
  • Requires additional setup and configuration compared to simpler solutions

Code Comparison

Formik:

import { Formik, Form, Field } from 'formik';

<Formik initialValues={{ name: '' }} onSubmit={handleSubmit}>
  <Form>
    <Field name="name" />
    <button type="submit">Submit</button>
  </Form>
</Formik>

Nice Modal React:

import NiceModal, { useModal } from '@ebay/nice-modal-react';

const Modal = NiceModal.create(() => {
  const modal = useModal();
  return (
    <div>
      <h2>Modal Content</h2>
      <button onClick={() => modal.hide()}>Close</button>
    </div>
  );
});

While Formik focuses on form management, Nice Modal React specializes in modal handling. Formik provides a more structured approach to forms with built-in state management, whereas Nice Modal React offers a simpler API for creating and managing modals in React applications.

Accessible modal dialog component for React

Pros of react-modal

  • More established and widely used in the React community
  • Offers greater customization options for modal styling and behavior
  • Provides built-in accessibility features, including ARIA attributes

Cons of react-modal

  • Requires more boilerplate code to set up and manage modal state
  • Less intuitive API for managing multiple modals in complex applications
  • Lacks built-in support for stacking or nesting modals

Code Comparison

react-modal:

import Modal from 'react-modal';

function App() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <Modal isOpen={isOpen} onRequestClose={() => setIsOpen(false)}>
      <h2>Modal Content</h2>
    </Modal>
  );
}

nice-modal-react:

import NiceModal, { useModal } from '@ebay/nice-modal-react';

const MyModal = NiceModal.create(() => {
  const modal = useModal();
  return (
    <div className="modal">
      <h2>Modal Content</h2>
      <button onClick={() => modal.hide()}>Close</button>
    </div>
  );
});

function App() {
  return <button onClick={() => NiceModal.show(MyModal)}>Open Modal</button>;
}

The code comparison demonstrates that nice-modal-react offers a more declarative and concise approach to modal management, while react-modal requires manual state handling. nice-modal-react's API simplifies the process of showing and hiding modals, potentially reducing boilerplate in complex applications.

Bootstrap components built with React

Pros of react-bootstrap

  • Comprehensive set of pre-built components based on Bootstrap
  • Extensive documentation and community support
  • Seamless integration with existing Bootstrap stylesheets

Cons of react-bootstrap

  • Larger bundle size due to the extensive component library
  • Less flexibility in customizing modal behavior compared to nice-modal-react
  • Steeper learning curve for developers unfamiliar with Bootstrap

Code Comparison

nice-modal-react:

import { create, show } from 'nice-modal-react'

const MyModal = create(() => {
  return <div>Modal content</div>
})

show(MyModal)

react-bootstrap:

import { Modal, Button } from 'react-bootstrap'

function MyModal() {
  const [show, setShow] = useState(false)
  return (
    <Modal show={show} onHide={() => setShow(false)}>
      <Modal.Body>Modal content</Modal.Body>
    </Modal>
  )
}

nice-modal-react focuses on simplifying modal management with a create-and-show pattern, while react-bootstrap provides a more traditional approach with state management. nice-modal-react offers a more concise API for modal creation and display, whereas react-bootstrap's Modal component is part of a larger ecosystem of Bootstrap-based components.

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.

Pros of Material-UI

  • Comprehensive UI component library with a wide range of pre-built components
  • Follows Google's Material Design principles, ensuring a consistent and modern look
  • Large community and extensive documentation for easier implementation and troubleshooting

Cons of Material-UI

  • Larger bundle size due to the extensive component library
  • Steeper learning curve for customization and theming
  • May require more setup and configuration for specific use cases

Code Comparison

Material-UI modal implementation:

import { Modal, Box } from '@mui/material';

function BasicModal() {
  return (
    <Modal open={open} onClose={handleClose}>
      <Box sx={style}>
        <h2>Modal Title</h2>
        <p>Modal content goes here</p>
      </Box>
    </Modal>
  );
}

nice-modal-react implementation:

import NiceModal, { useModal } from '@ebay/nice-modal-react';

const MyModal = NiceModal.create(() => {
  const modal = useModal();
  return (
    <div className="modal">
      <h2>Modal Title</h2>
      <p>Modal content goes here</p>
      <button onClick={() => modal.hide()}>Close</button>
    </div>
  );
});

nice-modal-react focuses specifically on modal management, offering a simpler API for handling modals in React applications. Material-UI provides a more comprehensive set of UI components but may require additional setup for modal functionality.

An enterprise-class UI design language and React UI library

Pros of ant-design

  • Comprehensive UI component library with a wide range of pre-built components
  • Extensive documentation and community support
  • Highly customizable with theming capabilities

Cons of ant-design

  • Larger bundle size due to its extensive feature set
  • Steeper learning curve for beginners
  • May require additional configuration for optimal performance

Code Comparison

ant-design:

import { Modal } from 'antd';

const showModal = () => {
  Modal.info({
    title: 'This is a notification message',
    content: (
      <div>
        <p>some messages...some messages...</p>
      </div>
    ),
    onOk() {},
  });
};

nice-modal-react:

import NiceModal, { useModal } from '@ebay/nice-modal-react';

const MyModal = NiceModal.create(() => {
  const modal = useModal();
  return (
    <Modal visible={modal.visible} onOk={modal.hide} onCancel={modal.hide}>
      <p>Modal content</p>
    </Modal>
  );
});

While ant-design provides a more comprehensive set of UI components, nice-modal-react focuses specifically on modal management with a simpler API. ant-design offers greater flexibility and customization options but comes with a larger footprint. nice-modal-react provides a more lightweight solution for modal handling, potentially easier to integrate into existing projects.

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.

Pros of Headless UI

  • Provides a comprehensive set of unstyled, accessible UI components
  • Offers flexibility in styling and customization
  • Integrates seamlessly with Tailwind CSS

Cons of Headless UI

  • Requires more setup and configuration for basic functionality
  • May have a steeper learning curve for beginners

Code Comparison

nice-modal-react:

import { create, useModal } from '@ebay/nice-modal-react';

const MyModal = create(() => {
  const modal = useModal();
  return <div>Modal content</div>;
});

MyModal.show();

Headless UI:

import { Dialog } from '@headlessui/react';
import { useState } from 'react';

function MyModal() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
      <Dialog.Panel>Modal content</Dialog.Panel>
    </Dialog>
  );
}

Summary

Headless UI offers a more comprehensive set of UI components with greater flexibility in styling, while nice-modal-react provides a simpler API for modal management. Headless UI may require more initial setup but offers more customization options, whereas nice-modal-react focuses specifically on modal functionality with a more straightforward implementation.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Nice Modal

This is a small, zero dependency utility to manage modals in a natural way for React. It uses context to persist state of modals globally so that you can show/hide a modal easily either by the modal component or id.

You can also see the introduction at eBay tech blog.

Also check out our another nice utility! nice-form-react! 😜

NPM Downloads Build Status Coverage Status Demo MIT licensed

For example, you can use below code to show a modal anywhere:

import NiceModal from '@ebay/nice-modal-react';
import MyModal from './MyModal';

//...
NiceModal.show(MyModal, { someProp: 'hello' }).then(() => {
  // do something if the task in the modal finished.
});
//...

Or you can register the modal with an id so that you don't need to import the modal component to use it:

import NiceModal from '@ebay/nice-modal-react';
import MyModal from './MyModal';

NiceModal.register('my-modal', MyModal);

// you can use the string id to show/hide the modal anywhere
NiceModal.show('my-modal', { someProp: 'hello' }).then(() => {
  // do something if the task in the modal finished.
});
//...

NOTE: @ebay/nice-modal-react is not a React modal component but should be used with other modal/dialog implementations by UI libraries like Material UI, Ant.Design, Bootstrap React, etc.

Examples

You can see a list of examples at: https://ebay.github.io/nice-modal-react

Key Features

  • Zero dependency and small: ~2kb after gzip.
  • 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.
  • The code of your modal component is not executed if 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.
  • Easy to integrate with any UI library.

Motivation

Using modals in React is a bit frustrating. Think of that if you need to implement the below UI:

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, and parameters in the container component. Declaring it everywhere means managing the state everywhere. It's frustrating.

The other option put it in the Root component, for example:

const Root = () => {
  const [visible, setVisible] = useState(false);
  // other logic ...
  return (
    <>
      <Main />
      <NewTicketModal visible={visible} />
    </>
  );
}

However, when you declare the modal in the root component, there are some issues:

  1. Not scalable. It's unreasonable to maintain the modal's state in the root component. When you need more modals you need to maintain much state, especially you need to maintain arguments for the modal.
  2. It's hard to show or hide the modal from children components. When you maintain the state in a component then you need to pass setVisible down to the place where you need to show or hide the modal. It makes things too complicated.

Unfortunately, most examples of using modals just follow this practice, it causes such confusions when managing modals in React.

I believe you must once encountered with the scenario that originally you only needed to show a modal when you click a button, then when requirements changed, you need to open the same modal from a different place. Then you have to refactor your code to re-consider where to declare the modal. The root cause of such annoying things is just because we have not understood the essentials of a modal.

Rethink the Modal Usage Pattern in React

According to the Wikipedia, a modal can be described as:

A window that prevents the user from interacting with your application until he closes the window.

From the definition, we can get a conclusion: a modal is a global view that's not necessarily related with a specific context.

This is very similar with the page concept in a single page UI application. The visibility/ state of modals should be managed globally because, from the UI perspective, a modal could be shown above any page/component. The only difference between a modal and a page is: a modal allows you to not leave the current page to do some separate tasks.

For pages management, we already have router framework like React Router, it helps to navigate to a page by URL. Actually, you can think of a URL as a global id for a page. So, similarly, what if you assign a unique id to a modal and then show/hide it by the id? This is just how we designed NiceModal.

Usage

Installation

# with yarn
yarn add @ebay/nice-modal-react

# or with npm
npm install @ebay/nice-modal-react

Create Your Modal Component

With NiceModal you can create a separate modal component easily. It's just the same as creating a normal component but wrapping it with high order component by NiceModal.create. For example, the below code shows how to create a dialog with Ant.Design:

import { Modal } from 'antd';
import NiceModal, { useModal } from '@ebay/nice-modal-react';

export default NiceModal.create(({ name }: { name: string }) => {
  // Use a hook to manage the modal state
  const modal = useModal();
  return (
    <Modal
      title="Hello Antd"
      onOk={() => modal.hide()}
      visible={modal.visible}
      onCancel={() => modal.hide()}
      afterClose={() => modal.remove()}
    >
      Hello {name}!
    </Modal>
  );
});

From the code, we can see:

  • The modal is uncontrolled. You can hide your modal inside the component regardless of where it is shown.
  • The high order component created by NiceModal.create ensures your component is not executed before it becomes visible.
  • You can call modal.remove to remove your modal component from the React component tree to reserve transitions.

Next, let's see how to use the modal.

Using Your Modal Component

There are very flexible APIs for you to manage modals. See below for the introduction.

Embed your application with NiceModal.Provider:

Since we will manage the status of modals globally, the first thing is embedding your app with NiceModal provider, for example:

import NiceModal from '@ebay/nice-modal-react';
ReactDOM.render(
  <React.StrictMode>
    <NiceModal.Provider>
      <App />
    </NiceModal.Provider>
  </React.StrictMode>,
  document.getElementById('root'),
);

The provider will use React context to maintain all modals' state.

Using the modal by component

You can control a nice modal by the component itself.

import NiceModal from '@ebay/nice-modal-react';
import MyAntdModal from './my-antd-modal'; // created by above code

function App() {
  const showAntdModal = () => {
    // Show a modal with arguments passed to the component as props
    NiceModal.show(MyAntdModal, { name: 'Nate' })
  };
  return (
    <div className="app">
      <h1>Nice Modal Examples</h1>
      <div className="demo-buttons">
        <button onClick={showAntdModal}>Antd Modal</button>
      </div>
    </div>
  );
}

Use the modal by id

You can also control a nice modal by id:

import NiceModal from '@ebay/nice-modal-react';
import MyAntdModal from './my-antd-modal'; // created by above code

// If you use by id, you need to register the modal component.
// Normally you create a modals.js file in your project
// and register all modals there.
NiceModal.register('my-antd-modal', MyAntdModal);

function App() {
  const showAntdModal = () => {
    // Show a modal with arguments passed to the component as props
    NiceModal.show('my-antd-modal', { name: 'Nate' })
  };
  return (
    <div className="app">
      <h1>Nice Modal Examples</h1>
      <div className="demo-buttons">
        <button onClick={showAntdModal}>Antd Modal</button>
      </div>
    </div>
  );
}

Use modal with the hook

The useModal hook can not only be used inside a modal component but also any component by passing it a modal id/component:

import NiceModal, { useModal } from '@ebay/nice-modal-react';
import MyAntdModal from './my-antd-modal'; // created by above code

NiceModal.register('my-antd-modal', MyAntdModal);
//...
// if you use with id, you need to register it first
const modal = useModal('my-antd-modal');
// or if with component, no need to register
const modal = useModal(MyAntdModal);

//...
modal.show({ name: 'Nate' }); // show the modal
modal.hide(); // hide the modal
//...

Declare your modal instead of register

The nice modal component you created can be also used as a normal component by JSX, then you don't need to register it. For example:

import NiceModal, { useModal } from '@ebay/nice-modal-react';
import MyAntdModal from './my-antd-modal'; // created by above code

function App() {
  const showAntdModal = () => {
    // Show a modal with arguments passed to the component as props
    NiceModal.show('my-antd-modal')
  };
  return (
    <div className="app">
      <h1>Nice Modal Examples</h1>
      <div className="demo-buttons">
        <button onClick={showAntdModal}>Antd Modal</button>
      </div>
      <MyAntdModal id="my-antd-modal" name="Nate" />
    </div>
  );
}

With this approach, you can get the benefits:

  • Inherit React context in the modal component under some component node.
  • Pass arguments to the modal component via props.

NOTE: If you attempt to show the component by ID but the modal is not declared or registered, nothing will happen except for a warning message in the dev console.

Using promise API

Besides using props to interact with the modal from the parent component, you can do more easily by promise. For example, we have a user list page with an add user button to show a dialog to add user. After user is added the list should refresh itself to reflect the change, then we can use below code:

NiceModal.show(AddUserModal)
  .then(() => {
    // When call modal.resolve(payload) in the modal component
    // it will resolve the promise returned by `show` method.
    // fetchUsers will call the rest API and update the list
    fetchUsers()
  })
  .catch(err=> {
    // if modal.reject(new Error('something went wrong')), it will reject the promise
  }); 

You can see the live example on codesandbox.

Integrating with Redux

Though not necessary, you can integrate Redux to manage the state of nice modals. Then you can use Redux dev tools to track/debug state change of modals. Here is how to do it:

// First combine the reducer
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';
import NiceModal from '@ebay/nice-modal-react';
import { Button } from 'antd';
import { MyAntdModal } from './MyAntdModal';
import logger from 'redux-logger';

const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
const enhancer = composeEnhancers(applyMiddleware(logger));

const store = createStore(
  combineReducers({
    modals: NiceModal.reducer,
    // other reducers...
  }),
  enhancer,
);

// Passing Redux state to the nice modal provider
const ModalsProvider = ({ children }) => {
  const modals = useSelector((s) => s.modals);
  const dispatch = useDispatch();
  return (
    <NiceModal.Provider modals={modals} dispatch={dispatch}>
      {children}
    </NiceModal.Provider>
  );
};

export default function ReduxProvider({ children }) {
  return (
    <Provider store={store}>
      <ModalsProvider>{children}</ModalsProvider>
    </Provider>
  );
}

Using with any UI library

NiceModal provides lifecycle methods to manage the state of modals. You can use modal handler returned by useModal hook to bind any modal-like component to the state. Below are typical states and methods you will use:

  • *modal.visible: the visibility of a modal.
  • *modal.hide: will hide the modal, that is, change modal.visible to false.
  • *modal.remove: remove the modal component from the tree so that your modal's code is not executed when it's invisible. Usually, you call this method after the modal's transition.
  • *modal.keepMounted if you don't want to remove the modal from the tree for some instances, you can decide if call modal.remove based on the value of keepMounted.

Based on these properties/methods, you can easily use NiceModal with any modal-like component provided by any UI libraries.

Using help methods

As you already saw, we use code similar with below to manage the modal state:

//...
const modal = useModal();
return (
  <Modal
    visible={modal.visible}
    title="Hello Antd"
    onOk={() => modal.hide()}
    onCancel={() => modal.hide()}
    afterClose={() => modal.remove()}
  >
    Hello NiceModal!
  </Modal>
);
//...

It binds visible property to the modal handler, and uses modal.hide to hide the modal when close button is clicked. And after the close transition it calls modal.remove to remove the modal from the dom node.

For every modal implementation, we always need to do these bindings manually. So, to make it easier to use we provided helper methods for 3 popular UI libraries Material UI, Ant.Design and Bootstrap React.

import NiceModal, {
  muiDialog,
  muiDialogV5,
  antdModal,
  antdModalV5,
  antdDrawer,
  antdDrawerV5,
  bootstrapDialog
} from '@ebay/nice-modal-react';

//...
const modal = useModal();
// For MUI
<Dialog {...muiDialog(modal)}>

// For MUI V5
<Dialog {...muiDialogV5(modal)}>

// For ant.design
<Modal {...antdModal(modal)}>

// For ant.design v4.23.0 or later
<Modal {...antdModalV5(modal)}>

// For antd drawer
<Drawer {...antdDrawer(modal)}>

// For antd drawer v4.23.0 or later
<Drawer {...antdDrawerV5(modal)}>

// For bootstrap dialog
<Dialog {...bootstrapDialog(modal)}>

These helpers will bind modal's common actions to correct properties of the component. However, you can always override the property after the helper's property. For example:

const handleSubmit = () => {
  doSubmit().then(() => {
    modal.hide();
  });
}
<Modal {...antdModal(modal)} onOk={handleSubmit}>

In the example, the onOk property will override the result from antdModal helper.

API Reference

https://ebay.github.io/nice-modal-react/api/

Testing

You can test your nice modals with tools like @testing-library/react.

import NiceModal from '@ebay/nice-modal-react';
import { render, act, screen } from '@testing-library/react';
import { MyNiceModal } from '../MyNiceModal';

test('My nice modal works!', () => {
  render(<NiceModal.Provider />
  
  act(() => {
    NiceModal.show(MyNiceModal);
  });
  
  expect(screen.getByRole('dialog')).toBeVisible();
});

Contribution Guide

# 1. Clone repo
git clone https://github.com/eBay/nice-modal-react.git

# 2. Install deps
cd nice-modal-react
yarn

# 3. Make local repo as linked
yarn link

# 4. Start dev server
yarn dev

# 5. Install examples deps
cd example
yarn

# 6. Use local linked lib
yarn link @ebay/nice-modal-react

# 7. Start examples dev server
yarn start

Then you can access http://localhost:3000 to see the examples.

FAQ

Can I get context in the component tree in a modal?

Yes. To get the data from context in the component tree you need to use the declarative way. For example:

export default function AntdSample() {
  return (
    <>
      <Button type="primary" onClick={() => NiceModal.show('my-antd-modal', { name: 'Nate' })}>
        Show Modal
      </Button>
      <MyAntdModal id="my-antd-modal" {...otherProps} />
    </>
  );
}

See more here.

License

MIT

NPM DownloadsLast 30 Days