简体   繁体   中英

React ES6 component modal API

I would like to create component API for dialog window, based on https://github.com/reactjs/react-modal

I want render react component with renderDOM, get component instance and call API of modal, it does mean for example open(), close() etc.

So, more precisely I would like to work with current state of component, (like API works) not with props.

I have simple base class for all modals:

export class BaseModal extends Modal {

    constructor() {
        super();

        this.state = BaseModal._getInitState();          
    }

    static get style() {
        return {
            content: {
                top: '50%',
                left: '50%',
                right: 'auto',
                bottom: 'auto',
                marginRight: '-50%',
                transform: 'translate(-50%, -50%)'
            }
        };
    }

    open() {
        this.setState({isOpen: true});
    }

    close() {
        this.setState({isOpen: false});
    }

    render() {
        return this.state.isOpen ? (
            <div className="modal modal__{this.name}">

                <Modal isOpen={this.state.isOpen}
                       onRequestClose={this.close}
                       style={BaseModal.style}
                       contentLabel={this.getHeaderContent()}
                       parentSelector={BaseModal._getParentSelector}>

                    <button onClick={this.close}>X</button>

                    <div className="modal__body">
                        {this.getBodyContent()}
                    </div>

                    <div className="modal__footer">
                        {this.getFooterContent()}
                    </div>
                </Modal>
            </div>
        ) : null;
    }

    getHeaderContent() {
        throw new Error("Not implement in child class.");
    }

    getBodyContent() {
        throw new Error("Not implement in child class.");
    }

    getFooterContent() {
        throw new Error("Not implement in child class.");
    }   

    static _getInitState() {
        let state = {};
        state.isOpen = false;
    }       
}

Now I have child component:

export class RecommendTripModal extends BaseModal {

    getHeaderContent() {
        return "Test modal dialog";      
    }

    getBodyContent() {
        return <p>Test modal body</p>;
    }

    getFooterContent() {
        return <p>Test modal footer</p>;
    }
}

Ok, this is fine, but now I want to call something like this:

let recommendedTripModal = ReactDOM.render(React.createElement(RecommendTripModal, null), document.querySelector("#modals")); 

//open dialog
recommendedTripModal.open();

But now is problem with context. Because this.state.isOpen has RecommendTripModal context and state is null. Is there way, how to solved this problem with react? And is this solid way? Or I should create required API different way?

Thank you for your time!

Okay, let's dig a little bit deeper here, best way is to use React context and HoC magic power

Modal.js

import React from "react";
import PropTypes from "prop-types";
import { omit } from "lodash";

export class Modal extends React.Component {
  static contextTypes = {
    modalOpen: PropTypes.bool
  };

  static propTypes = {
    children: PropTypes.node
  };

  render() {
    if (!this.context.modalOpen) return null;

    return (
      <div>
        <h1>I am base modal title</h1>
        <div>{this.props.children}</div>
      </div>
    );
  }
}

export class ModalContext extends React.Component {
  static childContextTypes = {
    modalOpen: PropTypes.bool
  };

  static defaultProps = {
    onOpen: () => {},
    onClose: () => {}
  };

  static propTypes = {
    children: PropTypes.func.isRequired
  };

  constructor(...args) {
    super(...args);

    this.handleOpen = this.handleOpen.bind(this);
    this.handleClose = this.handleClose.bind(this);
  }

  state = {
    isOpen: false
  };

  getChildContext() {
    return {
      modalOpen: this.state.isOpen
    };
  }

  handleClose() {
    if (this.state.isOpen) {
      this.setState({ isOpen: false });
    }
  }

  handleOpen() {
    if (!this.state.isOpen) {
      this.setState({ isOpen: true });
    }
  }

  render() {
    const { identity, children } = this.props;
    return children({
      [identity]: {
        open: this.handleOpen,
        close: this.handleClose,
        isOpen: this.state.isOpen
      }
    });
  }
}

export default function modal(initialModalProps = {}) {
  return function(Component) {
    const componentName =
      Component.displayName || Component.name || "Component";

    return class extends React.Component {
      static displayName = `Modal(${componentName})`;

      static propTypes = {
        identity: PropTypes.string
      };

      static defaultProps = {
        identity: "modal"
      };

      render() {
        const { identity } = this.props;
        return (
          <ModalContext
            identity={identity}
            {...initialModalProps}
            {...this.props[identity]}
          >
            {modalProps => (
              <Component
                {...omit(this.props, identity, "identity")}
                {...modalProps}
              />
            )}
          </ModalContext>
        );
      }
    };
  };
}

HelloWorldModal.js

import React from "react";
import withModal, { Modal } from "./modal";

class HelloWorldModal extends React.Component {
  render() {
    const { modal } = this.props;
    return (
      <div>
        <button type="button" onClick={modal.open}>
          Open Modal
        </button>
        <button type="button" onClick={modal.close}>
          Close Modal
        </button>
        <Modal>Yeah! I am sample modal!</Modal>
      </div>
    );
  }
}

export default withModal()(HelloWorldModal);

In case you are lazy, I prepared a codesandbox with working code :)

https://codesandbox.io/s/2oxx2j4270?module=%2Fsrc%2FHelloWorldModal.js

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM