DEV Community

Oliver Reed
Oliver Reed

Posted on

Building Modal Dialogs with Reoverlay in React

Reoverlay is a powerful React library for managing modal dialogs and overlays declaratively. It provides a simple API for opening, closing, and managing multiple modals with built-in state management and lifecycle handling. This guide walks through setting up and creating modal dialogs using Reoverlay with React, from installation to a working implementation. This is part 29 of a series on using Reoverlay with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Basic knowledge of React hooks (useState, useEffect)
  • Familiarity with JavaScript/TypeScript

Installation

Install Reoverlay using your preferred package manager:

npm install reoverlay
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add reoverlay
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add reoverlay
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "reoverlay": "^4.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

Reoverlay requires a provider component at the root of your application. Update your main entry file:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ReoverlayProvider } from 'reoverlay';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <ReoverlayProvider>
      <App />
    </ReoverlayProvider>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a simple modal component. First, create a modal component file src/SimpleModal.jsx:

// src/SimpleModal.jsx
import React from 'react';
import { ModalContainer, ModalHeader, ModalBody, ModalFooter } from 'reoverlay';

function SimpleModal({ closeModal }) {
  return (
    <ModalContainer>
      <ModalHeader>
        <h2>Simple Modal</h2>
      </ModalHeader>
      <ModalBody>
        <p>This is a simple modal dialog created with Reoverlay.</p>
      </ModalBody>
      <ModalFooter>
        <button onClick={closeModal}>Close</button>
      </ModalFooter>
    </ModalContainer>
  );
}

export default SimpleModal;
Enter fullscreen mode Exit fullscreen mode

Now, create a component that opens the modal. Update your App.jsx:

// src/App.jsx
import React from 'react';
import { useReoverlay } from 'reoverlay';
import SimpleModal from './SimpleModal';
import './App.css';

function App() {
  const { openModal } = useReoverlay();

  const handleOpenModal = () => {
    openModal(SimpleModal);
  };

  return (
    <div className="App">
      <h1>Reoverlay Example</h1>
      <button onClick={handleOpenModal}>Open Modal</button>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a basic modal that opens when you click the button and closes when you click the close button.

Understanding the Basics

Reoverlay uses a declarative approach where:

  • ReoverlayProvider: Wraps your app and manages modal state
  • useReoverlay: Hook that provides openModal and closeModal functions
  • ModalContainer: Base component for modal structure
  • ModalHeader, ModalBody, ModalFooter: Pre-built sections for modal content
  • closeModal: Function passed to modal components to close them

Key concepts:

  • Declarative API: Open modals by passing the component directly
  • Automatic State Management: Reoverlay handles modal state internally
  • Component-based: Each modal is a React component
  • Lifecycle Management: Modals are automatically mounted/unmounted

Here's an example with custom styling:

// src/StyledModal.jsx
import React from 'react';
import { ModalContainer, ModalHeader, ModalBody, ModalFooter } from 'reoverlay';

function StyledModal({ closeModal }) {
  return (
    <ModalContainer
      style={{
        maxWidth: '500px',
        width: '90%',
        borderRadius: '8px',
        padding: '0'
      }}
    >
      <ModalHeader
        style={{
          padding: '20px',
          borderBottom: '1px solid #eee',
          backgroundColor: '#f8f9fa'
        }}
      >
        <h2 style={{ margin: 0 }}>Styled Modal</h2>
      </ModalHeader>
      <ModalBody style={{ padding: '20px' }}>
        <p>This modal has custom styling applied.</p>
      </ModalBody>
      <ModalFooter
        style={{
          padding: '20px',
          borderTop: '1px solid #eee',
          display: 'flex',
          justifyContent: 'flex-end',
          gap: '10px'
        }}
      >
        <button
          onClick={closeModal}
          style={{
            padding: '8px 16px',
            border: '1px solid #ddd',
            backgroundColor: 'white',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Cancel
        </button>
        <button
          onClick={closeModal}
          style={{
            padding: '8px 16px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Confirm
        </button>
      </ModalFooter>
    </ModalContainer>
  );
}

export default StyledModal;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a contact form modal with validation and data handling:

// src/ContactModal.jsx
import React, { useState } from 'react';
import { ModalContainer, ModalHeader, ModalBody, ModalFooter } from 'reoverlay';

function ContactModal({ closeModal }) {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };

  const validateForm = () => {
    const newErrors = {};
    if (!formData.name.trim()) {
      newErrors.name = 'Name is required';
    }
    if (!formData.email.trim()) {
      newErrors.email = 'Email is required';
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
      newErrors.email = 'Invalid email format';
    }
    if (!formData.message.trim()) {
      newErrors.message = 'Message is required';
    }
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!validateForm()) {
      return;
    }

    setIsSubmitting(true);
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('Form submitted:', formData);
      alert('Message sent successfully!');
      closeModal();
      setFormData({ name: '', email: '', message: '' });
    } catch (error) {
      console.error('Error submitting form:', error);
      alert('Failed to send message. Please try again.');
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <ModalContainer
      style={{
        maxWidth: '500px',
        width: '90%',
        borderRadius: '8px',
        padding: '0'
      }}
    >
      <ModalHeader
        style={{
          padding: '20px',
          borderBottom: '1px solid #eee',
          backgroundColor: '#f8f9fa'
        }}
      >
        <h2 style={{ margin: 0 }}>Contact Us</h2>
      </ModalHeader>
      <ModalBody style={{ padding: '20px' }}>
        <form onSubmit={handleSubmit} id="contact-form">
          <div style={{ marginBottom: '16px' }}>
            <label
              htmlFor="name"
              style={{
                display: 'block',
                marginBottom: '4px',
                fontWeight: '500'
              }}
            >
              Name *
            </label>
            <input
              type="text"
              id="name"
              name="name"
              value={formData.name}
              onChange={handleInputChange}
              style={{
                width: '100%',
                padding: '8px',
                border: `1px solid ${errors.name ? '#dc3545' : '#ddd'}`,
                borderRadius: '4px',
                fontSize: '14px'
              }}
            />
            {errors.name && (
              <div
                style={{
                  color: '#dc3545',
                  fontSize: '12px',
                  marginTop: '4px'
                }}
              >
                {errors.name}
              </div>
            )}
          </div>

          <div style={{ marginBottom: '16px' }}>
            <label
              htmlFor="email"
              style={{
                display: 'block',
                marginBottom: '4px',
                fontWeight: '500'
              }}
            >
              Email *
            </label>
            <input
              type="email"
              id="email"
              name="email"
              value={formData.email}
              onChange={handleInputChange}
              style={{
                width: '100%',
                padding: '8px',
                border: `1px solid ${errors.email ? '#dc3545' : '#ddd'}`,
                borderRadius: '4px',
                fontSize: '14px'
              }}
            />
            {errors.email && (
              <div
                style={{
                  color: '#dc3545',
                  fontSize: '12px',
                  marginTop: '4px'
                }}
              >
                {errors.email}
              </div>
            )}
          </div>

          <div style={{ marginBottom: '16px' }}>
            <label
              htmlFor="message"
              style={{
                display: 'block',
                marginBottom: '4px',
                fontWeight: '500'
              }}
            >
              Message *
            </label>
            <textarea
              id="message"
              name="message"
              value={formData.message}
              onChange={handleInputChange}
              rows={4}
              style={{
                width: '100%',
                padding: '8px',
                border: `1px solid ${errors.message ? '#dc3545' : '#ddd'}`,
                borderRadius: '4px',
                fontSize: '14px',
                resize: 'vertical',
                fontFamily: 'inherit'
              }}
            />
            {errors.message && (
              <div
                style={{
                  color: '#dc3545',
                  fontSize: '12px',
                  marginTop: '4px'
                }}
              >
                {errors.message}
              </div>
            )}
          </div>
        </form>
      </ModalBody>
      <ModalFooter
        style={{
          padding: '20px',
          borderTop: '1px solid #eee',
          display: 'flex',
          justifyContent: 'flex-end',
          gap: '10px'
        }}
      >
        <button
          type="button"
          onClick={closeModal}
          disabled={isSubmitting}
          style={{
            padding: '8px 16px',
            border: '1px solid #ddd',
            backgroundColor: 'white',
            borderRadius: '4px',
            cursor: isSubmitting ? 'not-allowed' : 'pointer',
            opacity: isSubmitting ? 0.6 : 1
          }}
        >
          Cancel
        </button>
        <button
          type="submit"
          form="contact-form"
          disabled={isSubmitting}
          style={{
            padding: '8px 16px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: isSubmitting ? 'not-allowed' : 'pointer',
            opacity: isSubmitting ? 0.6 : 1
          }}
        >
          {isSubmitting ? 'Sending...' : 'Send Message'}
        </button>
      </ModalFooter>
    </ModalContainer>
  );
}

export default ContactModal;
Enter fullscreen mode Exit fullscreen mode

Now update your App.jsx to use the contact modal:

// src/App.jsx
import React from 'react';
import { useReoverlay } from 'reoverlay';
import ContactModal from './ContactModal';
import './App.css';

function App() {
  const { openModal } = useReoverlay();

  const handleOpenContact = () => {
    openModal(ContactModal);
  };

  return (
    <div className="App" style={{ padding: '20px' }}>
      <h1>Reoverlay Contact Form Example</h1>
      <button
        onClick={handleOpenContact}
        style={{
          padding: '10px 20px',
          backgroundColor: '#007bff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '16px'
        }}
      >
        Contact Us
      </button>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Form handling with validation
  • Error display and styling
  • Async form submission
  • Loading states
  • Modal state management
  • Custom styling with inline styles

Common Issues / Troubleshooting

  1. Modal not displaying: Ensure ReoverlayProvider wraps your app at the root level. Check that you're using useReoverlay hook inside a component that's a child of the provider.

  2. closeModal not working: Make sure you're calling closeModal from the props passed to your modal component. The function is automatically provided by Reoverlay.

  3. Multiple modals: Reoverlay supports multiple modals. You can open multiple modals by calling openModal multiple times. They will be stacked and closed in reverse order.

  4. Styling issues: Use the style prop on ModalContainer and other modal components to customize appearance. For more complex styling, consider using CSS modules or styled-components.

  5. TypeScript support: Reoverlay has TypeScript definitions. Make sure you're using the correct types for modal components and props.

Next Steps

Now that you have a basic understanding of Reoverlay:

  • Learn about passing props to modals
  • Explore advanced features like modal stacking
  • Implement custom animations and transitions
  • Add accessibility enhancements
  • Learn about other modal libraries (react-modal, react-aria-modal)
  • Check the official repository: https://github.com/hiradary/reoverlay
  • Look for part 30 of this series for more advanced topics

Summary

You've successfully set up Reoverlay in your React application and created modal dialogs with form handling and validation. Reoverlay provides a declarative API for managing modals with built-in state management and lifecycle handling.

SEO Keywords

reoverlay
React modal library
reoverlay tutorial
React overlay management
reoverlay installation
React declarative modals
reoverlay example
React modal dialogs
reoverlay setup
React overlay provider
reoverlay modal container
React modal forms
reoverlay hooks
React modal state management
reoverlay getting started

Top comments (0)