nice-modal-react

A zero-dependency library that manages React modal state globally, so any component can show or hide a dialog by id without prop drilling.

Library
npm
v1.2.13
2,295stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
58/100Fair
Development Activity56
Maintenance28
Community52
Maturity56
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture62
Code Quality62
Innovation80
Learning Curve75

@ebay/nice-modal-react is a small React library from eBay that manages modal dialog state globally through React context, so any component can show or hide a modal by referencing its id or component reference rather than wiring up local state and prop drilling. It decouples modal invocation from modal declaration: calling NiceModal.show(MyModal, props) from anywhere in the tree mounts and shows the modal, and the library resolves a promise when the modal closes, so the caller can await the outcome like any other async operation.

Rather than providing its own dialog UI, NiceModal is a thin state-management layer meant to sit on top of existing modal/dialog components from libraries like Ant Design, Material UI, or Bootstrap React. It ships adapter helpers (antdModal, muiDialog, bootstrapDialog, and their v5 variants) that map its handler object directly onto those libraries’ visible/onOk/onCancel props, turning what is usually manual per-component wiring into a one-line binding.

What You Get

  • A NiceModal.Provider that maintains all modal visibility/args state via a single useReducer-driven context
  • Promise-based show()/hide() functions so a modal’s outcome can be awaited with .then()/.catch()
  • Component-based or id-based invocation, with optional NiceModal.register() for id lookups
  • Ready-made adapter helpers for Ant Design, Material UI, and Bootstrap React dialogs/drawers
  • A standalone reducer that can be plugged into an existing Redux store for devtools visibility
  • Delay-mounted show transitions that preserve enter/exit animations instead of popping in instantly

Common Use Cases

  • Triggering a confirmation dialog from a deeply nested component without lifting state to a parent
  • Registering shared modals (e.g. an add-user dialog) by id so any page can invoke them without importing the component
  • Wrapping an existing Ant Design/MUI/Bootstrap dialog with NiceModal.create() to replace local useState with global state
  • Refreshing a list after an async task in a modal by chaining .then(fetchUsers) on the promise returned by show()

Under The Hood

Architecture The entire public surface lives in one module (src/index.tsx, ~680 lines) built around a React context (NiceModalContext) paired with a useReducer store, a module-level MODAL_REGISTRY object mapping ids to components, and two promise-callback maps (modalCallbacks, hideModalCallbacks) that bridge the imperative show()/hide() calls back to awaitable promises. A deliberately mutable module-scoped dispatch reference is assigned inside the Provider so that non-component code (the standalone show/hide functions) can still reach into React state without every caller needing to be a component itself. Calling show() dispatches a reducer action that updates context state, which re-renders a NiceModalPlaceholder that mounts only registered-and-visible modals; components wrapped via create() read their own state back out through useModal() and drive a delayVisible flag through effects to preserve mount-then-show transitions. Because the registry, the reducer shape, and the placeholder’s rendering loop are all read directly by the UI-library adapter helpers, changing any of those shared data structures would ripple through every consumer at once.

Tech Stack Written in TypeScript targeting es5, compiled twice via tsc into separate ESM (esnext modules) and CJS output directories rather than bundled through webpack/Rollup/esbuild. React is a peer dependency (>16.8.0) and the library itself ships with zero runtime dependencies. Documentation is generated via TypeDoc from TSDoc comments on the exported API. Testing runs on Jest with the jest-circus runner, @testing-library/react and @testing-library/react-hooks for interaction assertions, and babel-preset-react-app purely as the Jest transform (not used for the shipped build).

Code Quality src/index.test.js is a substantial (400+ line) suite covering show/hide/remove, the reducer, and the useModal/create hook behavior end to end with @testing-library, with coverage collection configured directly in package.json. tsconfig enables several strict-adjacent flags (noImplicitAny, strictNullChecks, noUnusedLocals/Parameters) though not full strict mode, and ESLint is configured with typescript-eslint plus jest/testing-library plugins. The CI story is stale, though: the README’s build badge points at Travis CI via a checked-in .travis.yml, and no GitHub Actions workflow exists, so automated CI on the current default branch is unclear despite the local test suite being genuinely thorough.

API Design The public surface is intentionally tiny: a single default export object, one useModal() hook, and one create() HOC to wrap a modal component. Getting started needs almost no boilerplate — wrap the app in NiceModal.Provider, wrap a modal in NiceModal.create(), then call NiceModal.show(Component, props) from anywhere. Its standout ergonomic choice is the set of one-line adapter functions (antdModal, muiDialog, bootstrapDialog and v5 variants) that map its handler directly onto each UI library’s native visible/onOk/onCancel props, replacing manual per-component callback wiring, and the promise-based resolution lets a modal’s outcome be chained with .then()/.catch() like any other async call. Documentation (README, TypeDoc API reference, and live CodeSandbox examples) is thorough for a library of this size.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search