In this tutorial, we will explore nice-modal-react, which is a useful modal utility for React created by the developer team of eBay. They have been kind enough to make it accessible for the public after testing and using the utility internally for a year.

We will also build a demo app to apply all the reviewed features in practice. It is expected that we will be able to use modals to create new data, as well as edit and delete existing data:

Why use nice-modal-react?

The nice-modal-react package is a zero-dependency utility written in TypeScript and uses context to control the state of the modals throughout the entire app.

The main advantage of the utility is promise-based modal handling. This means instead of using props to interact with the component, you can use promises to update the state.

You can easily import the modal components throughout the app or use the specifications id component, so you do not have to import the component at all.

Closing modals is independent of the rest of the code, so you can close the component from the component itself, no matter where in the application it is shown.

 

It is crucial to understand that nice-modal-react is not the modal component itself. You will need to create the actual modals yourself (or use pre-built components from UI libraries like Material UIAnt design, or Chakra).

Creating react app

create a new React app on your local machine using the create-react-app tool by running this command:

npx create-react-app react-nice-modal

In case you choose to create the React app on your local machine, install React Query and the infinite scroller component using the command given below:

npm install --save @ebay/nice-modal-react
#or
yarn add @ebay/nice-modal-react

Setting up nice-modal-react

Open the index.js root file, import the NiceModal component, and wrap it around the App component:

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

At this point, we have set up the project to work with nice-modal-react, so we can start building individual components for the app.

Creating components

First, we need to create the individual files for the necessary components: ModalButton, and Note. To keep everything organized we will create a separate components folder and create a separate .js file and .css file for each component.

  • create Note component and note CSS in the components folder  and add code
import "./Note.css";
import Button from "./Button";
const Note = ({ title, onClickEdit, onClickDelete }) => {
  return (
    <div className="note">
      <p>{title}</p>
      <Button name="Edit" backgroundColor="gold" onClick={onClickEdit} />
      <Button name="Delete" backgroundColor="tomato" onClick={onClickDelete} />
    </div>
  );
};
export default Note;

 

Note.css

.note {
  display: grid;
  grid-template-columns: auto 70px 70px;
  gap: 20px;
  margin: 20px auto;
  text-align: left;
  word-break: break-all;
}
@media screen and (max-width: 400px) {
  .note {
    grid-template-columns: 1fr;
  }
}

 

  • After that create Button component and Button CSS in the components folder  and add code
import "./Button.css";
const Button = ({ name, backgroundColor, onClick }) => {
  return (
    <button className="button" onClick={onClick} style={{ backgroundColor }}>
      {name}
    </button>
  );
};
export default Button;

 

Button.css

.button {
  border: none;
  padding: 5px 10px;
  cursor: pointer;
  border-radius: 5px;
  width: 100%;
}

 

  • After that create Modal component and Modal CSS in the components folder  and add code
import { useState } from "react";
import NiceModal, { useModal } from "@ebay/nice-modal-react";
import "./Modal.css";
import Button from "./Button";
const Modal = NiceModal.create(
  ({ title, subtitle, action, bgColor, note = "" }) => {
    const [input, setInput] = useState(note);
    const modal = useModal();
    return (
      <div className="background">
        <div className="modal">
          <h1>{title}</h1>
          <p className="subtitle">{subtitle}</p>
          {action === "Save" && (
            <input
              className="input"
              type="text"
              value={input}
              onChange={(e) => {
                setInput(e.target.value);
              }}
            />
          )}
          <div className="actions">
            <Button
              name={action}
              backgroundColor={bgColor}
              onClick={() => {
                if (action === "Save") {
                  if (input) {
                    modal.resolve(input);
                    modal.remove();
                    console.log("Note saved");
                  } else {
                    console.log("Note is empty");
                  }
                } else {
                  modal.resolve();
                  modal.remove();
                  console.log("Note removed");
                }
              }}
            />
            <Button
              name="Cancel"
              backgroundColor="silver"
              onClick={() => {
                modal.remove();
              }}
            />
          </div>
        </div>
      </div>
    );
  }
);
export default Modal;

 

Modal.css

 

.background {
  width: 100vw;
  height: 100vh;
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  background-color: rgba(0, 0, 0, 0.7);
}
.modal {
  padding: 20px;
  width: 350px;
  border-radius: 5px;
  text-align: center;
  background-color: white;
  word-break: break-all;
}
.subtitle {
  margin-bottom: 20px;
}
.input {
  width: 100%;
  height: 25px;
  border: 1px solid silver;
  border-radius: 5px;
  padding: 0px 10px;
}
.actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-top: 20px;
}

 

 

Implementing the logic

Now, let’s put everything together and create logic for our app. Open App.js and include this code:

import { useState } from "react";
import NiceModal from "@ebay/nice-modal-react";
import Modal from "./components/Modal";
import Note from "./components/Note";
import Button from "./components/Button";
import "./styles.css";
const noteList = [
  "CodeSolution first note",
  "CodeSolution second note",
  "CodeSolution third note"
];
const getNoteIndex = (e) =>
  Array.from(e.target.parentElement.parentNode.children).indexOf(
    e.target.parentElement
  );
export default function App() {
  const [notes, setNotes] = useState(noteList);
  const showAddModal = () => {
    NiceModal.show(Modal, {
      title: "Add a new note",
      subtitle: "Enter the title",
      action: "Save",
      bgColor: "limegreen"
    }).then((note) => {
      setNotes([note, ...notes]);
    });
  };
  const showEditModal = (e) => {
    NiceModal.show(Modal, {
      title: "Edit the note",
      subtitle: "Rename the Title",
      action: "Save",
      bgColor: "gold",
      note: notes[getNoteIndex(e)]
    }).then((note) => {
      const notesArr = [...notes];
      notesArr[getNoteIndex(e)] = note;
      setNotes(notesArr);
    });
  };
  const showDeleteModal = (e) => {
    NiceModal.show(Modal, {
      title: "Confirm Delete",
      subtitle: `The "${notes[getNoteIndex(e)]}" will be permamently removed`,
      action: "Delete",
      bgColor: "tomato",
      note: notes[getNoteIndex(e)]
    }).then(() => {
      const notesArr = [...notes];
      notesArr.splice(getNoteIndex(e), 1);
      setNotes(notesArr);
    });
  };
  return (
    <div className="App">
      <h1>Note List</h1>
      <p style={{ marginBottom: "20px" }}>Using nice-modal-react <br /> yarn add @ebay/nice-modal-react / npm i -s @ebay/nice-modal-react</p>
      <Button
        name="Add"
        backgroundColor="limegreen"
        onClick={() => {
          showAddModal();
        }}
      />
      <div>
        {notes.map((note, index) => {
          return (
            <Note
              key={index}
              title={note}
              onClickEdit={showEditModal}
              onClickDelete={showDeleteModal}
            />
          );
        })}
      </div>
    </div>
  );
}

 

 

Styles.css

 

 

Also, rename App.css to styles.css and remove the index.css file. In the newly renamed styles.css file, include the following style rules:

 

@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap");
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: "Montserrat", sans-serif;
}
#root{
  background: #dddded;
  height: 100vh;
  padding: 20px;
}
body {
  
}
.App {
  padding: 10px;
  max-width: 500px;
  margin: 0 auto;
  text-align: center;
  background: #fff;
}

 

 

First, we imported the useState hook to keep track of the notes object once we update it when using the app. We also imported the NiceModal component and every individual component we created in the previous phase.

To style the component, we’ll use an external stylesheet we created.

Then we created an noteList array that will hold the sample notes for the application. We also created the getNoteIndex function so we are able to identify the index of the particular note the user clicks in the list.

Inside the App function, we first set the sample notes list to the notes variable. Then we created three different functions to handle the add, edit, and delete button clicks.

Each function opens up the modal and passes in the necessary props we defined in the Modal component. Once the save or delete button is pressed, the notes list gets updated accordingly.

Finally, we rendered the titlesubtitle of the application, added the Add button with the necessary props, and looped through the notes variable to display all the notes.

Everything is organized and there is not a single state variable for the modal itself, yet we are successfully handling three different modals.

At this point, you should have a working demo. Let’s test it out!

Make sure your React app is still running in the terminal. If not, run npm start again. Now, open the browser and navigate to http://localhost:3000. You should be presented with a fully functional CRUD Notes demo app.

 

 

Thanks  

 

About the author
Sudarshan Vishwakarma

sudarshan.vis101@gmail.com

Discussion
  • 0 comments

Add comment To Login
Add comment