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?
4 Answers
Reset to default 2 +50I 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.