Complete website in Rs. 5,000 with Free Hosting & Domain. Offer ends in  00:00:00
Back to Blog

Building Custom React Modal Wrapper with Enhanced Flexibility

Create custom React modal wrapper using ReactModal, offering flexibility with dynamic header, footer, and body sections. This solution provides better customization options while maintaining clean code and performance

Oct 14, 2024 Updated: Oct 14, 2024

In many React projects, modals are an essential component for user interaction, allowing forms, alerts, or other content to be presented without leaving the current page. While there are plenty of libraries available, such as ReactModal, you may need more customization in specific use cases. In this post, we’ll walk through building a custom modal wrapper using ReactModal with enhanced flexibility, including customizable headers, footers, and optional close buttons.

Why Customize ReactModal?

ReactModal is a powerful and flexible tool, but for more advanced use cases, you may find yourself needing to:

  1. Define custom styles for the modal’s body, header, and footer.
  2. Separate header, footer, and body content while maintaining clean structure.
  3. Add dynamic close buttons and other features without rewriting your modal logic each time.

By wrapping ReactModal in a reusable custom component, we can achieve all these goals while keeping our code clean and modular.

The Custom Modal Component

Here’s the complete code for our custom modal component. It extends the basic functionality of ReactModal by allowing custom styles and the ability to inject specific modal sections, such as headers and footers, dynamically.

"use client";

import { X } from "lucide-react";
import { Children, HTMLAttributes, isValidElement } from "react";
import ReactModal, { Props as ReactModalProps, Styles as ReactModalStyles } from "react-modal";

// Extend ReactModal styles to include custom body styles
type MyModalStyles = ReactModalStyles & {
  body?: React.CSSProperties;
};

// ModalHeader component
type HeaderProps = HTMLAttributes<HTMLDivElement> & {
  children?: React.ReactNode;
};

export function ModalHeader({ children, ...props }: HeaderProps) {
  return <div data-my-modal="header" {...props}>{children}</div>;
}

// ModalFooter component
type FooterProps = HTMLAttributes<HTMLDivElement> & {
  children?: React.ReactNode;
};

export function ModalFooter({ children, ...props }: HeaderProps) {
  return <div data-my-modal="footer" {...props}>{children}</div>;
}

// Main Modal component
type Props = ReactModalProps & {
  onClose: () => void;
  hideCloseButton?: boolean;
  style?: MyModalStyles;
};

export function Modal({
  isOpen,
  onClose,
  style,
  hideCloseButton = false,
  children,
  ...props
}: Props) {
  const defaultStyles: MyModalStyles = {
    overlay: {
      zIndex: 20,
      height: '100%',
      display: 'flex',
      padding: '20px',
      alignItems: 'center',
      backdropFilter: 'blur(2px)',
      backgroundColor: 'rgba(0, 0, 0, 0.7)',
    },
    content: {
      inset: 0,
      width: '800px',
      maxWidth: '100%',
      margin: 'auto',
      maxHeight: '100%',
      padding: '0',
      position: 'relative',
      display: 'flex',
      flexDirection: 'column',
    },
    body: {
      height: '100%',
      flexGrow: '1',
      overflowY: 'auto',
      scrollbarWidth: 'thin',
    },
  };

  // Merge default styles with user-provided styles
  const mergedStyles = {
    overlay: {
      ...defaultStyles.overlay,
      ...style?.overlay,
    },
    content: {
      ...defaultStyles.content,
      ...style?.content,
    },
    body: {
      ...defaultStyles.body,
      ...style?.body,
    },
  };

  // Separate ModalHeader and ModalFooter from children
  let modalHeader: React.ReactNode = null;
  let modalFooter: React.ReactNode = null;
  const modalBody: React.ReactNode[] = [];

  Children.forEach(children, (child) => {
    if (isValidElement(child) && child.type === ModalHeader) {
      modalHeader = child;
    } else if (isValidElement(child) && child.type === ModalFooter) {
      modalFooter = child;
    } else {
      modalBody.push(child);
    }
  });

  return (
    <ReactModal
      isOpen={isOpen}
      style={mergedStyles}
      closeTimeoutMS={300}
      onRequestClose={onClose}
      {...props}
    >
      {/* Render ModalHeader if provided */}
      {modalHeader && modalHeader}
      <div data-my-modal="body" style={mergedStyles.body}>
        {modalBody}
      </div>
      {modalFooter && modalFooter}
      {
        !hideCloseButton &&
        <button
          type="button"
          onClick={onClose}
          aria-label="Close"
          title="Close"
          className="absolute top-0 right-0 px-3 py-3 leading-none bg-transparent"
        >
          <X className="w-6 h-6" />
        </button>
      }
    </ReactModal>
  );
}

Key Features of the Custom Modal

The modal allows for custom header and footer sections through ModalHeader and ModalFooter components. This separation ensures that you can easily customize your modal layout while maintaining a clean structure.

<Modal isOpen={isOpen} onClose={closeModal}>
  <ModalHeader>My Modal Title</ModalHeader>
  <p>This is the body content of the modal.</p>
  <ModalFooter>
    <button onClick={onClose}>Close</button>
  </ModalFooter>
</Modal>

Dynamic Close Button

You can choose to hide the close button by passing hideCloseButton as a prop. If the button is not hidden, an “X” icon will be rendered in the top-right corner, allowing users to close the modal easily.

<Modal isOpen={isOpen} onClose={closeModal} hideCloseButton={true}>
  {/* Modal content */}
</Modal>

Customizable Styles

The modal has default styles, but these can be overridden by passing a style prop. This gives you full control over the look and feel of the modal, including the overlay, content, and body sections.

<Modal
  isOpen={isOpen}
  onClose={closeModal}
  style={{
    content: { backgroundColor: '#fff' },
    body: { padding: '20px' },
  }}
>
  {/* Modal content */}
</Modal>

Conclusion

This custom modal wrapper gives you all the flexibility you need while maintaining a clean and modular codebase. You can easily manage headers, footers, and body content separately while ensuring a consistent user experience. Moreover, the ability to toggle the close button and apply custom styles makes it highly adaptable to various project needs.

By wrapping ReactModal in this manner, you can reuse the component throughout your application and make changes easily, saving you time and effort in the long run.

Contact

Got A Question For Us?

Feel free to ask anything directly on call or fill the form and we will contact back within few hours.