The status quo

In react development, various modals and drawers are one of our indispensable interactions. In the case of Modal components, the following page should be familiar if you are working with a background system

This is probably the way most people would start off by creating the index.js file, which is composed of the Table + Button component.

import React, { useState } from 'react';
import { Table, Button, Divider} from 'antd';
import OpenModal from './openModal';
import ReactDOM from 'react-dom';
import './index.css';

const data = [{
  name: 'Joe'.age: '12'
}, {
  name: 'tom'.age: '22'
}]

const App = () = > {
  const [visible, setVisible] = useState(false);
  const [record, setRecord] = useState({});
  const columns = [
    {
      dataIndex: 'name'.title: 'name'}, {dataIndex: 'age'.title: 'age'
    }, {
      title: 'operation'.render: (text, record) = > {
        return <div>
          <a onClick={()= >ShowModal (record)} > edit</a>
          <Divider type="vertical" />
          <a >To view</a>
          <Divider type="vertical" />
          <a >delete</a>
        </div>}}]const showModal = (record) = > {
    setVisible(true);
    if (record) {
      setRecord(record)
    }
  };

  const handleOk = () = > {
    setVisible(false);
  };

  const handleCancel = () = > {
    setVisible(false);
  };

  return (
    <>
      <Button type="primary" onClick={showModal}>Add personnel</Button>
      <Table
        dataSource={data}
        columns={columns}
      />
      <OpenModal
        handleOk={handleOk}
        handleCancel={handleCancel}
        visible={visible}
        setVisible={setVisible}
        record={record}
      />
    </>
  );
};

ReactDOM.render(<App />.document.getElementById('root'));
Copy the code

Then create openmodal.js with the child modal component in it:

import React from 'react';
import {Modal} from 'antd';

const MemberModal = (props) = > {
  return <Modal
    title={'new'}visible={props.visible}
    onCancel={props.handleCancel}
    onOk={props.handleOk}
  >
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
  </Modal>
};

export default MemberModal;
Copy the code

Pain points

This way of writing has several painful points:

  • Many if the parent component page interaction is more complexmodalNeed to open, you may need for each onemodalSet one for bothvisibleAnd the correspondingsetVisibleFunction to control them separately.
  • Table component has edit function, open editmodalBox, you need to place the current row data (record) tomodalInside, most of the action is in the edit function willrecordenduresstateInside and then in the parent componentrenderI’m going to put this inside the functionstatetomodalComponents.

Both of these add an extra state variable, indirectly making the code less readable; And if you have more than three modals in the same component, the readability of the code will get worse if you don’t do something about it.

To solve

For the first pain point, you can extract the repetitive logical visible variable and setVisible function; For the second pain point, if the rendering time of the floating layer component can be controlled in the edit function, then there is no need to save a record in the state.

It’s natural to think of higher-order components. Take a look at the React website for higher-order components. First is a function, followed by a function that takes a component and returns a value for the new component.

Now that you know the requirements you want, you can begin to implement them. First define a wrapper function that receives a component, second I want to render modal where the parent component triggers the open float function, so I need to return a render method. The source code is as follows:

import React from 'react';
import ReactDOM from 'react-dom';

const wrapper = (component) = > {
  // Destroy the component
  const destoryDialog = (element) = > {
    const unmountResult = ReactDOM.unmountComponentAtNode(element);
    if(unmountResult && element.parentNode) {
      setTimeout(() = > {
        element.parentNode.removeChild(element);
      }, 300); }}// Render component
  const render = ({element, component, config}) = > {
    constcomInstance = React.createElement(component, { ... config,key: 'div'.closeDialog: () = > {
        destoryDialog(element)
      },
      visible: true
    })
    ReactDOM.render(comInstance, element)
  }
  return function (config) { / / mount div
    const element = document.createElement('div');
    render({element, component, config});
    document.getElementsByTagName('body') [0].appendChild(element); }};export default wrapper;
Copy the code

It’s also easy to use. For modal child components, you just need to introduce the wrapper function

import React from 'react';
import {Modal} from 'antd';
+ import wrapper from 'xxxx';Const MemberModal = (props) => {return <Modal title={' new '} visible={props. Visible}- onCancel={props.handleCancel}
- onOk={props.handleOk}
+ onCancel={props.closeDialog}
+ onOk={props.closeDialog}> <p>Some contents... </p> <p>Some contents... </p> <p>Some contents... </p> </Modal> };- export default MemberModal;
+ export default wrapper(MemberModal);
Copy the code

The parent component also needs to be modified a little.

import React, { useState } from 'react'; import { Table, Button, Divider} from 'antd'; import MemberModal from './openModal'; import ReactDOM from 'react-dom'; import './index.css'; Const data = [{name: 'zhang' age: '12'}, {name: "Tom, age: '22'}] const App () = = > {- const [visible, setVisible] = useState(false);
- const [record, setRecord] = useState({});
  const columns = [
    {
      dataIndex: 'name',Title: 'name ', width: '40%'},{dataIndex: 'age',{title: 'age ', width: '40%'}, {title:' operation ', render: (text, record) => {return <div>< a onClick = {() = > showModal (' edit 'record)} > edit < / a >
-  showModal(record)}><Divider type=" Divider "/> <a > view </a> <Divider type=" Divider" /> <a > delete </a> </div>}}] record = {}) => {+ MemberModal({
+ type,
+ record,
+})
- setVisible(true);
- if (record) {
- setRecord(record)
-}
  };

  const handleOk = () => {
    setVisible(false);
  };

  const handleCancel = () => {
    setVisible(false);
  };

  return (
    <>
      <Button
        style={{float: 'right', marginBottom: '12px'}}
        type="primary"
+ onClick={() => showModal('add')}
- onClick={showModal}<Table rowKey={(record) => record. Name} dataSource={data} columns={columns} />- 
- handleOk={handleOk}
- handleCancel={handleCancel}
- visible={visible}
- setVisible={setVisible}
- record={record}
- />< / a >); }; ReactDOM.render(<App />, document.getElementById('root'));Copy the code

Open the add button and edit button, respectively, to print the subcomponentpropsWe can get throughtypeValue of the different to determine the opening is new operation or edit operation, so as to do some logic processing, very convenient; And in the parent component, there is no need torecordThat’s stored in state.

The new problem

The idea of higher-order components solves two of the previous pain points, but creates new problems

  • Floating layers will be rerendered on and offdomAnd destructiondomNot very elegant in performance.
  • Because it’s dead in the codeappendChildtobodyThe floating layer cannot be hung below the current parent component
  • Unable to get child component’s REF

The last

The performance problems are generally perceived within the project as being within the scope of acceptance (as if there were no obvious changes). Cannot hang below the current parent component… This will have to be wrapped without a wrapper function. It is not recommended to operate the real DOM through the ref in react. So far, there is no floating layer scenario where the ref is not allowed. If there is, the wrapper function can only be used.

Leave a comment and share your tips. Learn from each other and grow together.