preface

First, let’s take a look at the first demo of Modal on the Ant Design website

import { Modal, Button } from 'antd';



class App extends React.Component {

  state = { visiblefalse };



  showModal = (a)= > {

    this.setState({

      visibletrue.

    });

  };



  handleOk = e= > {

    console.log(e);

    this.setState({

      visiblefalse.

    });

  };



  handleCancel = e= > {

    console.log(e);

    this.setState({

      visiblefalse.

    });

  };



  render() {

    return (

      <div>

        <Button type="primary" onClick={this.showModal}>

          Open Modal

        </Button>

        <Modal

          title="Basic Modal"

          visible={this.state.visible}

          onOk={this.handleOk}

          onCancel={this.handleCancel}

        >


          <p>Some contents...</p>

          <p>Some contents...</p>

          <p>Some contents...</p>

        </Modal>

      </div>


    );

  }

}

Copy the code

Of course, in general, we write Modal is not as simple as the example in the official website, after all, so simple would be more inclined to use an API like modal. confirm and pop up directly. We might re-encapsulate Modal, write some code logic in it and it might be fixed like title directly in the component, and then expose some APIS like Visible, onOk, and onCancel with props.

Promoting Visible to the parent component solves the problem, but it also creates a problem. Every time we open the popover, since visible is in the parent component, the parent component will render again, and even if the other children in the parent component are not optimized (not using memo or shouldComponentUpdate), It will also be rerendered.

So is there any way to solve this problem? Sure, we just need to leave the Visible state in the modal-related child component. In the parent component, all we really need is to open the popover and receive the child component’s callback. So what are some ways to keep Visible in child components? I’m going to go through them, because I can’t think of any names, so I’m going to go one, two, three, four, EMMM, and that’s it.

The specific implementation

Online code

Codesandbox address

Plan a

import React, { memo, useState } from "react";

import { Modal } from "antd";



type Modal1Props = {

  children: React.ReactElement;

onOk? () :void;

onCancel? () :void;

  [others: string] :any;

};



const Modal1 = memo<Modal1Props>(({ children, onOk, onCancel, ... _restProps }) = > {

  const [visible, setVisible] = useState(false);



  const wrapWithClose = (method? : () = >void) = > () = > {

    setVisible(false);

    method && method();

  };



  // ------



  return (

    <>

      <Modal

Title =" Plan 1"

        visible={visible}

        onOk={wrapWithClose(onOk)}

        onCancel={wrapWithClose(onCancel)}

      >

<div>... </div>

      </Modal>

      {React.cloneElement(children, {

        onClick: (. args:any[]) = > {

          const { onClick } = children.props;

          setVisible(true);

          onClick && onClick(. args);

        }

      }
)}

    </>

  
);

}
);



export default Modal1;

Copy the code

The first option is more opportunistic, but it also has its disadvantages, is that the popover operation can only be done by one element and no more.

Scheme 2

Ref is a natural way to manipulate child component state in a parent component, so let’s look at how to do that.

import React, { useState, useImperativeHandle, useRef } from "react";

import { Modal } from "antd";



type Payload = {

onOk? () :void;

onCancel? () :void;

  [others: string] :any;

};



export type Modal2RefType = {

  show(payload: Payload): void;

};



const Modal2 = React.forwardRef<Modal2RefType>((_props, ref) = > {

  const [visible, setVisible] = useState(false);

  const payloadRef = useRef<Payload>({});



  useImperativeHandle(

    ref,

    () = > ({

      show: payload => {

        payloadRef.current = payload;

        setVisible(true);

      }

    }
),

    []

  
);



  const wrapWithClose = (method? : () = >void) = > () = > {

    setVisible(false);

    method && method();

  };



  return (

    <Modal

Title =" Plan 2"

      visible={visible}

      onOk={wrapWithClose(payloadRef.current.onOk)}

      onCancel={wrapWithClose(payloadRef.current.onCancel)}

    >

<div>... </div>

    </Modal>

  
);

}
);



export default Modal2;

Copy the code

Using ref is also easy. Here we pass some extra parameters using show instead of props as in scenario 1, but we need an extra variable to store them. Suffice it to say, this is not perfect.

Plan 3

For controlling child components in the parent component, we can certainly use the “omnipotent” publish subscription, because publish subscriptions are not what we’re talking about here, so we’ll simply guide the package, using Eventemitter3.

import React, { memo, useState, useRef, useEffect } from "react";

import { Modal } from "antd";

import EventEmitter from "eventemitter3";



const eventEmitter = new EventEmitter();



type Payload = {

onOk? () :void;

onCancel? () :void;

  [others: string] :any;

};



type ModalType = React.NamedExoticComponent & { show(payload: Payload): void };



const Modal3: ModalType = memo(

  (_props, ref) = > {

    const [visible, setVisible] = useState(false);

    const payloadRef = useRef<Payload>({});



    useEffect((a)= > {

      const handler = (payload: Payload) = > {

        setVisible(true);

        payloadRef.current = payload;

      };



      eventEmitter.on("show", handler);



      return (a)= > eventEmitter.off("show", handler);

} []);



    const wrapWithClose = (method? : () = >void) = >() = > {

      setVisible(false);

      method && method();

    };



    return (

      <Modal

        title="Plan three"

        visible={visible}

        onOk={wrapWithClose(payloadRef.current.onOk)}

        onCancel={wrapWithClose(payloadRef.current.onCancel)}

      >

<div>... </div>

      </
Modal>

    );

  },

  (a)= > true

as any;



Modal3.show = (payload: Payload) = > eventEmitter.emit("show", payload);



export default Modal3;

Copy the code

In the code above, exporting eventEmitter directly would be less elegant (use elegant when you don’t know how to describe it, probably). We also need to know that it is not elegant to call the emit method to trigger the show event, so we will bind a show method to Modal3.

After looking at the code above, I think some people will realize that there’s no need to include an eventEmitter for this. It’s a bit of a leap. Why don’t we just assign handler directly to modal3.show in useEffect? So, we have plan four

Plan 4

import React, { memo, useState, useRef, useEffect } from "react";

import { Modal } from "antd";



type Payload = {

onOk? () :void;

onCancel? () :void;

  [others: string] :any;

};



type ModalType = React.NamedExoticComponent & { show(payload: Payload): void };



const Modal4: ModalType = memo(

  (_props, ref) = > {

    const [visible, setVisible] = useState(false);

    const payloadRef = useRef<Payload>({});



    useEffect((a)= > {

      const lastShow = Modal4.show;



      Modal4.show = (payload: Payload) = > {

        setVisible(true);

        payloadRef.current = payload;

      };



      return (a)= > (Modal4.show = lastShow);

} []);



    const wrapWithClose = (method? : () = >void) = >() = > {

      setVisible(false);

      method && method();

    };



    return (

      <Modal

        title="Plan 4"

        visible={visible}

        onOk={wrapWithClose(payloadRef.current.onOk)}

        onCancel={wrapWithClose(payloadRef.current.onCancel)}

      >

<div>... </div>

      </
Modal>

    );

  },

  (a)= > true

as any;



Modal4.show = (payload: Payload) = > console.log("Modal4 is not mounted.");



export default Modal4;

Copy the code

More thinking

There are several solutions mentioned above. In fact, we can improve the state even further by using the Context, receiving the SHOW API in the parent component that doesn’t change, receiving the visible and payload that changes in the component where Modal is, Of course, I thought it would be too complicated, so I didn’t list it. See here, I think we all know that I must be the most recommended scheme four, the reason why all write out, is to tell you that we should have more thinking, rather than using a method to solve, is really master. Of course the above is I can think of the method, of course there will be I can’t think of, if you think of any other method, please comment.

Beyond that, I’d like to leave you with a few more questions:

  • There is a lot of reusable logic in the above code, so how?
  • We can find that the above implementation is no mattershowHow many times is the same popover, then what methods can be implemented similar toModal.confirmThe effect?
  • Ant DesignIn themessageHow should the component be implemented and, more specifically, what if you want to limit the number of messages that colleagues appear?

The first time to write an article because I am not good at expression, so I posted a lot of code, I beg your pardon, I will slowly exercise myself to write more words. If necessary, I will also answer the above questions one by one, in the way of more articles.

So finally, if you find the article useful, click follow.