Create React app with nice-modal-react module to Improve modal management
modal
React
Improve
nice
module
management
App
- By Sudarshan Vishwakarma
- Feb 19th, 2022
- 0 comments
- 14
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 UI, Ant 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: Modal
, Button
, 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 title
, subtitle
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