Newer
Older
CrypticOreWallet / src / layouts / dialogs / ModalDialog.tsx
@Drew Lemmy Drew Lemmy on 29 Sep 2020 2 KB refactor: forms in ModalDialog
import React, { Component, ReactNode } from "react";

import Modal from "react-bootstrap/Modal";
import { CloseButton } from "./utils/CloseButton";

import { Form, Formik, FormikHelpers, FormikProps, FormikValues } from "formik";

import { isFunction, noop } from "@utils";

interface Props<V extends FormikValues = FormikValues> {
  show: boolean;

  title: string;

  handleClose?: () => void;
  hasCloseButton?: boolean;
  hasFooterCloseButton?: boolean;

  buttons?: ((props: FormikProps<V>) => React.ReactNode) | React.ReactNode;

  initialValues?: V;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  onSubmit?: (values: V, formikHelpers: FormikHelpers<V>) => void | Promise<any>;
  children?: ((props: FormikProps<V>) => React.ReactNode) | React.ReactNode;
}

export class ModalDialog<V extends FormikValues = FormikValues> extends Component<Props<V>> {
  render(): ReactNode {
    const { 
      hasCloseButton, hasFooterCloseButton, handleClose, // Close button
      show, title, // Modal state and title
      children, buttons, // Actual contents
      initialValues, onSubmit // Form nonsense
    } = this.props;

    if ((hasCloseButton || hasFooterCloseButton) && !handleClose)
      throw new Error("FormDialog has close button but no close handler");

    // The contents of the modal (header, body, footer), which may or may not
    // be wrapped in a form.
    const modalContents = (formikBag?: FormikProps<V>) => <>
      <Modal.Header closeButton={hasCloseButton}>
        <Modal.Title>{title}</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        {/* Custom modal body */}
        {isFunction(children) && formikBag
          ? (children as (bag: FormikProps<V>) => React.ReactNode)(formikBag)
          : children}
      </Modal.Body>
      <Modal.Footer>
        {/* Display the footer close button if we were asked to */}
        {hasFooterCloseButton && <CloseButton handleClose={handleClose || noop} />}

        {/* Display the custom buttons if provided */}
        {isFunction(buttons) && formikBag
          ? (buttons as (bag: FormikProps<V>) => React.ReactNode)(formikBag)
          : buttons}
      </Modal.Footer>
    </>;

    return (
      /* TODO: Animation is disabled for now, because react-bootstrap (or more
        specifically, react-transition-group) has an incompatibility with
        strict mode. */
      <Modal 
        show={show} centered animation={false} // TODO
        onHide={handleClose}
      >
        {initialValues && onSubmit 
          /* If this is a form, wrap the contents in Formik: */
          ? <Formik 
            initialValues={initialValues}
            onSubmit={onSubmit}>
            {(formikBag) => 
              <Form noValidate onSubmit={formikBag.handleSubmit}>
                {modalContents(formikBag)}
              </Form>
            }
          </Formik>
          /* Otherwise, render the contents directly: */
          : modalContents()}
      </Modal>
    );
  }
};