• Stars
    star
    2,063
  • Rank 22,400 (Top 0.5 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 3 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A modal state manager for React.

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.

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 implementions 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 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, parameters in the container component. Declaring it everywehre means managing 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 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 essential 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 showed above any page/component. The only difference between modal and 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 URL a global id for a page. So, similarly, what if you assign a uniq id to a modal then show/hide it by the id? This is just how we designed NiceModal.

However, besides using id, NiceModal allows to use the modal component directly to manage it.

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 you create a normal component but wrap it with high order compponent by NiceModal.create. For example, 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 }) => {
  // 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 where it is showed.
  • 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 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 use by id, 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 use with id, 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 benifits:

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

NOTE: if you show the component by id but the modal is not declared or registered. Nothing will happen but only 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 it easier by promise. For example, we have a user list page with a 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 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 lifecyle 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 state 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 you 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 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 use 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 dom node.

For every modal implementation we always need to these binding manually. So, to make it easier to use we provides 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 helpers 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

More Repositories

1

NMessenger

A fast, lightweight messenger component built on AsyncDisplaykit and written in Swift
Swift
2,422
star
2

akutan

A distributed knowledge graph store
Go
1,654
star
3

tsv-utils

eBay's TSV Utilities: Command line tools for large, tabular data files. Filtering, statistics, sampling, joins and more.
D
1,419
star
4

bayesian-belief-networks

Pythonic Bayesian Belief Network Package, supporting creation of and exact inference on Bayesian Belief Networks specified as pure python functions.
Python
1,122
star
5

NuRaft

C++ implementation of Raft core logic as a replication library
C++
1,010
star
6

restcommander

Fast Parallel Async HTTP client as a Service to monitor and manage 10,000 web servers. (Java+Akka)
Java
899
star
7

parallec

Fast Parallel Async HTTP/SSH/TCP/UDP/Ping Client Java Library. Aggregate 100,000 APIs & send anywhere in 20 lines of code. Ping/HTTP Calls 8000 servers in 12 seconds. (Akka) www.parallec.io
Java
810
star
8

HeadGazeLib

A library to empower iOS app control through head gaze without a finger touch
Swift
754
star
9

Sequence-Semantic-Embedding

Tools and recipes to train deep learning models and build services for NLP tasks such as text classification, semantic search ranking and recall fetching, cross-lingual information retrieval, and question answering etc.
Python
460
star
10

modanet

ModaNet: A large-scale street fashion dataset with polygon annotations
336
star
11

flutter_glove_box

Various eBay tools for Flutter development
Dart
319
star
12

Neutrino

Neutrino is a software load balancer(SLB)
Scala
306
star
13

KPRN

Reasoning Over Knowledge Graph Paths for Recommendation
Lua
280
star
14

UAF

UAF - Universal Authentication Framework
Java
276
star
15

griffin

Model driven data quality service
JavaScript
240
star
16

cors-filter

CORS (Cross Origin Resource Sharing) is a mechanism supported by W3C to enable cross origin requests in web-browsers. CORS requires support from both browser and server to work. This is a Java servlet filter implementation of server-side CORS for web containers such as Apache Tomcat.
Java
231
star
17

Jungle

An embedded key-value store library specialized for building state machine and log store
C++
224
star
18

sbom-scorecard

Generate a score for your sbom to understand if it will actually be useful.
Go
221
star
19

ebayui-core

Collection of Marko widgets; considered to be the core building blocks for all eBay components, pages & apps
TypeScript
217
star
20

jsonpipe

A lightweight AJAX client for chunked JSON responses
JavaScript
204
star
21

ebay-font

A small utility to efficiently load custom web fonts
JavaScript
175
star
22

skin

Pure CSS framework designed & developed by eBay for a branded, e-commerce marketplace.
JavaScript
171
star
23

accelerator

The Accelerator is a tool for fast and reproducible processing of large amounts of data.
Python
150
star
24

firebase-remote-config-monitor

Monitors firebase remote config values, posting changes to slack
JavaScript
139
star
25

maxDNN

High Efficiency Convolution Kernel for Maxwell GPU Architecture
C++
132
star
26

go-ovn

A Go library for OVN Northbound/Southbound DB access using native OVSDB protocol
Go
108
star
27

Gringofts

Gringofts makes it easy to build a replicated, fault-tolerant, high throughput and distributed event-sourced system.
C++
102
star
28

parallec-samples

Single file examples and ready-to-use servers show how to use parallec.io library. Examples to aggregate APIs and publish to Elastic Search and Kafka, and many more. www.parallec.io
Java
92
star
29

userscript-proxy

HTTP proxy to inject scripts and stylesheets into existing sites.
JavaScript
87
star
30

xcelite

Java
81
star
31

RANSynCoders

Jupyter Notebook
81
star
32

mindpatterns

HTML Accessibility Pattern Examples
HTML
79
star
33

ebay-oauth-python-client

Python OAuth SDK: Get OAuth tokens for eBay public APIs
Python
78
star
34

figma-include-accessibility-annotations

Include is a tool built to make annotating for accessibility (a11y) easierโ€”easier for designers to spec and easier for developers to understand what is required.
JavaScript
77
star
35

embedded-druid

Java
75
star
36

ebay-oauth-nodejs-client

๐Ÿ”‘ Generate an OAuth token that can be used to call the eBay Developer REST APIs.
JavaScript
66
star
37

Design-Grid-Overlay

A Chrome extension to overlay a design grid on your web page; configurable to fit many design scenarios.
JavaScript
65
star
38

json-comparison

Powerful JSON comparison tool for identifying all the changes within JSON files
Java
63
star
39

xFraud

Jupyter Notebook
63
star
40

bascomtask

Lightweight parallel Java tasks
Java
62
star
41

DASTProxy

Java
58
star
42

ebay-oauth-csharp-client

eBay OAuth C# Client Library
C#
57
star
43

jsonex

Java Object Serializer and Deserializer to JSON Format. Focuses on configuration friendliness, arbitrary object serialization and compact JSON format
Java
56
star
44

nvidiagpubeat

nvidiagpubeat is an elastic beat that uses NVIDIA System Management Interface (nvidia-smi) to monitor NVIDIA GPU devices and can ingest metrics into Elastic search cluster, with support for both 6.x and 7.x versions of beats. nvidia-smi is a command line utility, based on top of the NVIDIA Management Library (NVML), intended to aid in the management and monitoring of NVIDIA GPU devices.
Go
54
star
45

ebay-oauth-java-client

eBay OAuth APIs client for Java
Java
50
star
46

nice-dag

nice-dag is a lightweight javascript library, which is used to present a DAG diagram.
TypeScript
50
star
47

AutoOpt

Automatic and Simultaneous Adjustment of Learning Rate and Momentum for Stochastic Gradient Descent
Python
45
star
48

SparkChamber

An event tracking framework for iOS
Swift
45
star
49

Winder

Winder is a simple state machine based on Quartz Scheduler. It helps to write multiple steps tasks on Quartz Scheduler. Winder derived from a state machine which is widly used in eBay Cloud. eBay Platform As A Service(PaaS) uses it to deploy software to hundreds of thousands virtual machines.
Java
45
star
50

GZinga

Java
43
star
51

YiDB

Java
43
star
52

block-aggregator

C++
41
star
53

collectbeat

Beats with discovery capabilities for environments like Kubernetes
Go
41
star
54

bsonpatch

A BSON implementation of RFC 6902 to compute the difference between two BSON documents
Java
39
star
55

Jenkins-Pipeline-Utils

Global Jenkins Pipeline Library with common utilities.
Groovy
39
star
56

cassandra-river

Cassandra river for Elastic search.
Java
38
star
57

nice-form-react

A meta based form builder for React.
TypeScript
35
star
58

arc

adaptive resources and components
JavaScript
35
star
59

regressr

A command line regression testing framework for testing HTTP services
Scala
34
star
60

ebashlib

A bash script battery which gathers several generic helper scripts for other repositories.
Shell
30
star
61

modshot

Takes screenshot of UI modules and compare with baselines using PhantomCSS
JavaScript
29
star
62

FeedSDK-Python

eBay Python Feed SDK - SDK for downloading large gzipped (tsv) item feed files and applying filters for curation
Python
29
star
63

ebayui-core-react

eBayUI React components
TypeScript
28
star
64

visual-html

Visual regression testing without the flakiness.
TypeScript
28
star
65

accessibility-ruleset-runner

eBay Accessibility Ruleset Runner automates 20% of WCAG 2.0 AA recommendations, saving time on manual testing.
JavaScript
27
star
66

crossdomain-xhr

JavaScript
27
star
67

oink

REST based interface for PIG execution
Java
27
star
68

bonsai

open source version of the Bonsai library
Scala
26
star
69

geosense

Self-contained jar to lookup timezone by lat+lon
Java
25
star
70

browser-telemetry

A Telemetry module for collecting errors, logs, metrics, uncaught exceptions etc on browser side.
JavaScript
25
star
71

oja

Lightweight Dependency Injection Framework for Node.JS Apps - Structure your application business logic
JavaScript
25
star
72

SketchSVG

Have icons in a Sketch file but don't want to manually extract and compress them as SVGs? Let our SketchSVG tool do it!
JavaScript
25
star
73

CustomRippleView

The Custom Ripple View library provides Android developers an easy way to customize and implement a Ripple Effect view.
Kotlin
24
star
74

FGrav

Dynamic Flame Graph Visualizations from raw data in your browser
JavaScript
24
star
75

nodash

Lightweight replacement for subset of Lodash
JavaScript
24
star
76

FeedSDK

Java SDK for downloading large gzipped (tsv) item feed files and applying filters for curation
Java
24
star
77

HomeStore

Storage Engine for block and key/value stores.
C++
22
star
78

kube-credentials-plugin

A Jenkins plugin to store credentials in kubernetes
Java
21
star
79

releaser

A declarative API that syncs specs from git to kubernetes
Go
20
star
80

airflow-rest-api-plugin

A plugin of Apache Airflow that exposes REST endpoints for custom REST APIs.
Python
20
star
81

mtdtool

The Manual Test Demultiplexer is a desktop app (Mac and Windows) that provides an interface for driving manual testing on multiple physical devices.
Java
20
star
82

graph-analytics-plugin

Gradle Project Graph Analysis and Reporting Plugin
Kotlin
19
star
83

EBNObservable

A block-based Key-Value Observing (KVO) implementation with observable collections.
Objective-C
19
star
84

skin-react

Skin components built with React (Typescript)
TypeScript
18
star
85

accelerator-project_skeleton

Python
18
star
86

taxonomy-sdk

An SDK designed to bring transparency to the rapid evolution of our aspects metadata for our partners.
Java
18
star
87

ebay-oauth-android-client

eBay OAuth Android Client library
Kotlin
17
star
88

wextracto

Python
17
star
89

myriad

Java
17
star
90

event-notification-nodejs-sdk

NodeJS SDK designed to simplify processing of eBay notifications.
JavaScript
17
star
91

TDD-Albums

A Hands-On Tutorial for iPhone Developers Learning TDD
17
star
92

lightning

Lightning is a Java based, super fast, multi-mode, asynchronous, and distributed URL execution engine from eBay
HTML
17
star
93

fluid

Fluid Web Components
JavaScript
16
star
94

ostara

Java
16
star
95

RTran

Road to Continous Upgrade
Scala
15
star
96

NautilusTelemetry

An iOS implementation of OpenTelemetry
Swift
15
star
97

hadoop-tsdb-connector

Java
15
star
98

Pine

Pine: Machine Learning Prediction As A Service
Scala
15
star
99

Flink-SQL-Extension

TypeScript
15
star
100

eslint-config-ebay

JavaScript
14
star