最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to render a popup by calling a function? - Stack Overflow

programmeradmin1浏览0评论

I want to render an element in React just by calling a function.

Usually you'd use a ponent (let's say a Popup) that takes a boolean from state to make it appear or not and change it with some callback handler. Something like this:

import React, { useState } from "react";

import Popup from "someponentlibrary";
import { Button } from "pathtoyourponents";

export const SomeComponent = () => {
    const [open, setOpen] = useState(false);

    return (
        <>
            <Button onClick={() => { setOpen(true); }}>
                this opens a modal
            </Button>
            <Popup type={"info"} open={open} timeout={1000}>
                text within modal
                <Button onClick={() => { setOpen(false); }}></Button>
            </Popup>
        </>
    );
};

I was wondering if instead of returning it in the ponent as above I could just call some method to just show it on the screen like that:

import React from "react";

import { Button, popup } from "pathtoyourponents";

export const SomeComponent = () => {
    return (
        <>
            <Button onClick={() => { popup.info("text within modal", 1000); }}>
                this opens a modal
            </Button>
        </>
    );
};

How do I write the popup function in order to render a Popup ponent in the DOM in such way?

I want to render an element in React just by calling a function.

Usually you'd use a ponent (let's say a Popup) that takes a boolean from state to make it appear or not and change it with some callback handler. Something like this:

import React, { useState } from "react";

import Popup from "someponentlibrary";
import { Button } from "pathtoyourponents";

export const SomeComponent = () => {
    const [open, setOpen] = useState(false);

    return (
        <>
            <Button onClick={() => { setOpen(true); }}>
                this opens a modal
            </Button>
            <Popup type={"info"} open={open} timeout={1000}>
                text within modal
                <Button onClick={() => { setOpen(false); }}></Button>
            </Popup>
        </>
    );
};

I was wondering if instead of returning it in the ponent as above I could just call some method to just show it on the screen like that:

import React from "react";

import { Button, popup } from "pathtoyourponents";

export const SomeComponent = () => {
    return (
        <>
            <Button onClick={() => { popup.info("text within modal", 1000); }}>
                this opens a modal
            </Button>
        </>
    );
};

How do I write the popup function in order to render a Popup ponent in the DOM in such way?

Share Improve this question asked Apr 8, 2021 at 17:22 BartBart 1722 gold badges5 silver badges12 bronze badges 0
Add a ment  | 

4 Answers 4

Reset to default 2 +50

I made an imperative API for a popup a while back, it allows you to await it until it closes, and even receive input to the caller (you might as well send back the button the user pressed):

const PopupContext = createContext()

export const PopupProvider = ({children}) => {
    const [open, setOpen] = useState(false)
    const [input, setInput] = useState('')
    const resolver = useRef()

    const handleOpen = useCallback(() => {
        const { promise, resolve } = createDeferredPromise()
        resolver.current = resolve
        setInput('')
        setOpen(true)

        return promise
    }, [])

    const handleClose = useCallback(() => {
        resolver.current?.(input)
        setOpen(false)
    }, [])

    return <PopupContext.Provider value={handleOpen}>
        {children}
        <Popup type={"info"} open={open} timeout={1000} onClose={handleClose}>
            <input value={input} onChange={e => setValue(e.target.value)}/>
            <Button onClick={handleClose}/>
         </Popup>
    </PopupContext.Provider>
}

export const usePopup = () => {
    const context = useContext(PopupContext);
    if (!context)
        throw new Error('`usePopup()` must be called inside a `PopupProvider` child.')

    return context
}

// used to let await until the popup is closed
const createDeferredPromise = func => {
    let resolve, reject
    const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
        func?.(resolve, reject)
    })

    return { promise, resolve, reject }
}

You can wrap you app with the provider:

return <PopupProvider>
    <App/>
</PopupProvider>

And use it inside your functional ponents:

const MyComponent = props => {
    const popup = usePopup()
    
    return <Button onClick={e => {
            const input = await popup()
            console.log('popup closed with input: ' + input)
        }/>
}

You can do much more interesting stuff, as pass prompt text to the popup function to show in the popup etc. I'll leave this up to you.

You might also want to memoize your top level ponent being wrapped to avoid rerendering the entire application on popup open/close.

You can use ReactDOM.render to render the popup when the function is called:

const node = document.createElement("div");
const popup = (message, {type, timeout}) => {
  document.body.appendChild(node);
  const PopupContent = () => {
    return (
      <Popup type={type} open={true} timeout={timeout}>
        {message}
        <button
          onClick={clear}
        >Close</button>
      </Popup >
    );
  };

  const clear = () => {
    ReactDOM.unmountComponentAtNode(node);
    node.remove();
  }
  
  ReactDOM.render(<PopupContent/>, node);
};

Then call the popup function:

 <Button onClick={() => { popup("text within modal", {type: "info", timeout: 1000}); }}>
    this opens a modal
 </Button>

The popup can be rendered as a new separate React app but can still be made to share state with the main app like below.

import React, { useEffect } from "react";
import { render, unmountComponentAtNode } from "react-dom";

const overlay = {
  top: "0",
  height: "100%",
  width: "100%",
  position: "fixed",
  backgroundColor: "rgb(0,0,0)"
};

const overlayContent = {
  position: "relative",
  top: "25%",
  textAlign: "center",
  margin: "30px",
  padding: "20px",
  backgroundColor: "white"
};

let rootNode;
let containerNode;

function Modal({ children }) {
  useEffect(() => {
    return () => {
      if (rootNode) {
        rootNode.removeChild(containerNode);
      }

      containerNode = null;
    };
  }, []);

  function unmountModal() {
    if (containerNode) {
      unmountComponentAtNode(containerNode);
    }
  }

  return (
    <div style={overlay}>
      <div style={overlayContent}>
        {children}
        <button onClick={unmountModal}>Close Modal</button>
      </div>
    </div>
  );
}

/* additional params like props/context can be passed */
function renderModal(Component) {
  if (containerNode) {
    return;
  }

  containerNode = document.createElement("div");
  rootNode = document.getElementById("root");
  containerNode.setAttribute("id", "modal");
  rootNode.appendChild(containerNode);

  render(<Modal>{Component}</Modal>, containerNode);
}

const App = () => {
  const ModalBody = <p>This is a modal</p>;

  return (
    <div>
      <button onClick={() => renderModal(ModalBody)}>Open Modal</button>
    </div>
  );
};

render(<App />, document.getElementById("root"));

Yes, I think you can make it.


for example:

Use popup ponent

import React, { useState } from "react";

import Popup from "someponentlibrary";
import { Button } from "pathtoyourponents";

export const SomeComponent = () => {
    const [open, setOpen] = useState(false);

    return (
        <>
            <Button onClick={() => { setOpen(true); }}>
                this opens a modal
            </Button>
            <Popup type={"info"} open={open} timeout={1000}>
                text within modal
                <Button onClick={() => { setOpen(false); }}></Button>
            </Popup>
        </>
    );
};

Define pop-up ponent

const Popup = (props) => {
  return(
    <div style={{zIndex:props.open?"-100":"100", transition: `all ${props.timeout / 
         1000}s`, opacity: props.open?1:0}}>
        {props.children}
    </div>
  )
}

I think you can customize the animation effect you like.

发布评论

评论列表(0)

  1. 暂无评论