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

I want to render an element in React just by calling a function.
Usually you'd use a component (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 "somecomponentlibrary";
import { Button } from "pathtoyourcomponents";
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 component 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 "pathtoyourcomponents";
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 component in the DOM in such way?

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>

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 components:
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 component being wrapped to avoid rerendering the entire application on popup open/close.

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 component
import React, { useState } from "react";
import Popup from "somecomponentlibrary";
import { Button } from "pathtoyourcomponents";
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 component
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.

Related

Is it possible to expose a function defined within a React function component to be called in other components?

I'm refactoring some old code for an alert widget and am abstracting it into its own component that uses DOM portals and conditional rendering. I want to keep as much of the work inside of this component as I possibly can, so ideally I'd love to be able to expose the Alert component itself as well as a function defined inside of that component triggers the render state and style animations so that no outside state management is required. Something like this is what I'm looking to do:
import Alert, { renderAlert } from '../Alert'
const CopyButton = () => (
<>
<Alert text="Text copied!" />
<button onClick={() => renderAlert()}>Copy Your Text</button>
</>
)
Here's what I currently have for the Alert component - right now it takes in a state variable from outside that just flips when the button is clicked and triggers the useEffect inside of the Alert to trigger the renderAlert function. I'd love to just expose renderAlert directly from the component so I can call it without the additional state variable like above.
const Alert = ({ label, color, stateTrigger }) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger])
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
};
export default Alert;
Though it's not common to "reach" into other components and invoke functions, React does allow a "backdoor" to do so.
useImperativeHandle
React.forwardRef
The idea is to expose out the renderAlert function imperatively via the React ref system.
Example:
import { forwardRef, useImperativeHandle } from 'react';
const Alert = forwardRef(({ label, color, stateTrigger }, ref) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger]);
useImperativeHandle(ref, () => ({
renderAlert,
}));
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
});
export default Alert;
...
import { useRef } from 'react';
import Alert from '../Alert'
const CopyButton = () => {
const ref = useRef();
const clickHandler = () => {
ref.current?.renderAlert();
};
return (
<>
<Alert ref={ref} text="Text copied!" />
<button onClick={clickHandler}>Copy Your Text</button>
</>
)
};
A more React-way to accomplish this might be to abstract the Alert state into an AlertProvider that renders the portal and handles the rendering of the alert and provides the renderAlert function via the context.
Example:
import { createContext, useContext, useState } from "react";
interface I_Alert {
renderAlert: (text: string) => void;
}
const AlertContext = createContext<I_Alert>({
renderAlert: () => {}
});
const useAlert = () => useContext(AlertContext);
const AlertProvider = ({ children }: { children: React.ReactElement }) => {
const [text, setText] = useState<string>("");
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
...
const renderAlert = (text: string): void => {
setAlertRendered(false);
setAlertVisible(false);
setText(text);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
const ele = <div>{alertRendered && <div> ..... </div>}</div>;
return (
<AlertContext.Provider value={{ renderAlert }}>
{children}
// ... portal ...
</AlertContext.Provider>
);
};
...
const CopyButton = () => {
const { renderAlert } = useAlert();
const clickHandler = () => {
renderAlert("Text copied!");
};
return (
<>
<button onClick={clickHandler}>Copy Your Text</button>
</>
);
};
...
function App() {
return (
<AlertProvider>
...
<div className="App">
...
<CopyButton />
...
</div>
...
</AlertProvider>
);
}

React : Open/close react-colorful picker

I'm using react-colorful to get colors HEX code.
I would want to show this component when the color's box is clicked, and hide it when clicked outside of it, kinda like the color picker Chrome is using (ex <input type="color" /> )
How to do so ?
https://codesandbox.io/s/react-colorful-demo-forked-wwxq2?file=/src/App.js:308-322
import React, { useState } from "react";
import { HexColorPicker } from "react-colorful";
import "./styles.css";
export default function App() {
const [color, setColor] = useState("#b32aa9");
const handleChange = (e) => {
setColor(e.target.value);
};
return (
<div className="App">
<HexColorPicker color={color} onChange={setColor} />
//Click on this box to show the picker <div className="value" style={{ borderLeftColor: color }}>
Current color is {color}
</div>
<input value={color} onChange={handleChange} />
<div className="buttons">
<button onClick={() => setColor("#c6ad23")}>Choose gold</button>
<button onClick={() => setColor("#556b2f")}>Choose green</button>
<button onClick={() => setColor("#207bd7")}>Choose blue</button>
</div>
</div>
);
}
At first, you should have to declare a state which tracks whether the color box is open or not like
const [open, setopen] = useState(false);
Now add an event handler on the div of the box which toggles the state from false to true and true to false.
const openBox = () => {
setopen(!open);
}
Now in your return statement, add a conditional to open or close colorBox. if clicked on that div which holds the onClick method.
<div className="App">
{open &&<HexColorPicker color={color} onChange={setColor} />
}
<div onClick={() => openBox()}className="value" style={{ borderLeftColor: color }}>
Current color is {color}
</div>
Now if you click on the div which contains the onClick method, it will open the colorBox and on pressing again it will close it.
You can use the following example (from https://codesandbox.io/s/opmco found in Code Recipes)
import React, { useCallback, useRef, useState } from "react";
import { HexColorPicker } from "react-colorful";
import useClickOutside from "./useClickOutside";
export const PopoverPicker = ({ color, onChange }) => {
const popover = useRef();
const [isOpen, toggle] = useState(false);
const close = useCallback(() => toggle(false), []);
useClickOutside(popover, close);
return (
<div className="picker">
<div
className="swatch"
style={{ backgroundColor: color }}
onClick={() => toggle(true)}
/>
{isOpen && (
<div className="popover" ref={popover}>
<HexColorPicker color={color} onChange={onChange} />
</div>
)}
</div>
);
};
useClickOutside.js
import { useEffect } from "react";
// Improved version of https://usehooks.com/useOnClickOutside/
const useClickOutside = (ref, handler) => {
useEffect(() => {
let startedInside = false;
let startedWhenMounted = false;
const listener = (event) => {
// Do nothing if `mousedown` or `touchstart` started inside ref element
if (startedInside || !startedWhenMounted) return;
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) return;
handler(event);
};
const validateEventStart = (event) => {
startedWhenMounted = ref.current;
startedInside = ref.current && ref.current.contains(event.target);
};
document.addEventListener("mousedown", validateEventStart);
document.addEventListener("touchstart", validateEventStart);
document.addEventListener("click", listener);
return () => {
document.removeEventListener("mousedown", validateEventStart);
document.removeEventListener("touchstart", validateEventStart);
document.removeEventListener("click", listener);
};
}, [ref, handler]);
};
export default useClickOutside;

Invoking a function from a child component in a parent component via callback

See codesandbox here
I want to use a callback in a child react component so that I can invoke a function from my child component in my parent component. However, when I handle the callback in my parent component, and try to set this child's function so that I can invoke this function later on when a button gets clicked, this function ends up getting invoked unexpectedly.
What I want to happen is that the sampleFunction to be invoked when the 'Invoke Sample Function' button is clicked, but instead, sampleFunction is invoked when the parent component is mounted (and the console is thus logged with 'foo'). How can I properly pass this callback function from the child to the parent? Thanks.
index.js:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
useEffect(() => {
callback({
toggleStatus,
sampleFunction: () => console.log("foo")
});
}, [callback, toggleStatus]);
const handleClick = () => {
setToggleStatus(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(null);
const handleCallback = props => {
setFn(props.sampleFunction);
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Based on what this question covers and the usage you want, you should use useCallback (docs - more info) instead of useEffect. something like the following is kind of better implementation:
import React from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
const handleClick = React.useCallback(() => {
setToggleStatus(prevState => !prevState);
callback({
toggleStatus,
sampleFunction: () => () => console.log("foo")
})
}, [callback, toggleStatus])
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(() => null);
const handleCallback = props => {
console.log(props)
setFn(props.sampleFunction);
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You need to update your code as you are calling the callback in the initialization of your child component.
useEffect(() => {
// remove below call
callback({
toggleStatus,
sampleFunction: () => console.log("foo")
});
}, [callback, toggleStatus]);
See codesandbox here
Thanks to #StackedQ and #Anthony for the help. As Anthony mentioned, in order to avoid an endless loop, I had to add an extra condition to handleCallback:
index.js:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
useEffect(() => {
callback({
toggleStatus,
sampleFunction: () => () => setToggleStatus(prevState => !prevState)
});
}, [callback, toggleStatus]);
const handleClick = () => {
setToggleStatus(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(null);
const handleCallback = props => {
if (!fn) {
setFn(props.sampleFunction);
}
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

How to move state outside of component using context provider

I currently have a preview component which has a reloading functionality attached into it using the useState hook. I now want the ability to refresh this component with the same functionality but with an external component. I know that this can be achieved by the useContext API, however i'm struggling to plug it all together.
Context:
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
const PreviewProvider = PreviewContext.Provider;
PreviewFrame:
const PreviewFrame = forwardRef((props, ref) => {
const { height, width } = props;
const classes = useStyles({ height, width });
return (
<Card className={classes.root} ref={ref}>
<div className={classes.previewWrapper} > {props.children} </div>
<div className={classes.buttonContainer}>
<IconButton label={'Refresh'} onClick={props.toggleReload} />
</div>
</Card>
);
});
PreviewFrameWrapped:
<PreviewFrame
toggleReload={props.toggleReload}
height={props.height}
width={props.width}
ref={frameRef}
>
<PreviewDiv isReloading={props.isReloading} containerRef={containerRef} height={height} width={width} />
</PreviewFrame>
const PreviewDiv = ({ isReloading, containerRef, height, width }) => {
const style = { height: `${height}px`, width: `${width}px`};
return !isReloading ?
<div className='div-which-holds-preview-content' ref={containerRef} style={style} />
: null;
};
Preview:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
So now i want to just be able to import the preview component and be able to refresh it using an external button, so not using the one that's already on the <PreviewFrame>.
I ideally want to consume it like this:
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
function PreviewControls () {
let { handleRefresh } = React.useContext(PreviewContext);
return <div><button onClick={handleRefresh}>↺ Replay</button></div>
}
Preview With My Attempt at Wrapping with Provider:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return (<PreviewProvider value={{ reloading: reloading, setReloading: setReloading, handleRefresh: toggleReload }} >
<PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
{/* it works if i put the external button called <PreviewControls> here*/}
</PreviewProvider>
);
}
So yeah as i said in the commented out block, it will work if put an external button there, however then that makes it attached/tied to the Preview component itself, I'm really not sure how to transfer the reloading state outside of the Preview into the Provider. Can someone please point out what i'm missing and what i need to do make it work in the way i want to.
All you need to do is to write a custom component PreviewProvider and store in the state of reloading and toggleReload function there. The preview and previewControls can consume it using context
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
export default function PreviewProvider({children}) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewContext.Provider value={{reloading, toggleReload}}>{children}</PreviewContext.Provider>
}
export default function Preview(props) {
const {reloading, toggleReload} = useContext(PreviewContext);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
function PreviewControls () {
let { toggleReload } = React.useContext(PreviewContext);
return <div><button onClick={toggleReload}>↺ Replay</button></div>
}
Finally using it like
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>

Writing a React hook to handle multiple divs visibility on mouse click

Just started to learn about Reack hooks but I cannot figure out if it is possible to write a simple hook (or should I use some other approach, e.g. useEffect along with useState) in order to control visibility of multiple elements by clicking on different buttons on page.
Let's say I have a simple app with 2 buttons and 2 "modal" windows:
const App = () => {
const [firstModalOpen, toggleFirstModal] = useState(false);
const [secondModalOpen, toggleSecondModal] = useState(false);
return (
<div>
<button onClick={() => toggleFirstModal(true)}>Open First Modal</button>
<button onClick={() => toggleSecondModal(true)}>Open Second Modal</button>
<FirstModal
{...props}
show={firstModalOpen}
toggleModal={toggleFirstModal}
/>
<SecondModal
{...props}
show={secondModalOpen}
toggleModal={toggleSecondModal}
/>
</div>
)
}
const FirstModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={firstModalOpen}
onHide={() => props.toggleModal(false)}
>
First modal content...
</Modal>
)
}
const SecondModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={secondModalOpen}
onHide={() => props.toggleModal(false)}
>
Second modal content...
</Modal>
)
}
// state hook attempt
const useToggleModal = () => (init) => {
const [show, setToggleModal] = useState(init);
const toggleModal = () => setToggleModal(!show);
return { show, toggleModal };
};
Since those are react-bootstrap modal windows, they use show and onHide properties to determine/handle visibility and I have to pass rest prop to avoid some side-effects.
If I'd use my hook attempt in my app, I'd handle both modals on any button click so I came up with the idea to pass a string (to both, buttons and modals) which would tell which modal exactly to handle, but that approach for some reason looked a bit wrong.
Is there a "smarter" way in React to handle this internally instead of passing strings around?
If you have multiple modals and only one of them needs to open at once, then you must use a single state which stores which modal is opened, kind of like a string having the id of the modal. However if you want to open multiple modals, you would store the isOpen prop differently
For the first case you would write your code like
const App = () => {
const [openModal, toggleModal] = useState('');
return (
<div>
<button onClick={() => toggleModal('first')}>Open First Modal</button>
<button onClick={() => toggleModal('second')}>Open Second Modal</button>
<FirstModal
{...props}
show={openModal === 'first'}
toggleModal={toggleModal}
/>
<SecondModal
{...props}
show={secondModalOpen}
toggleModal={toggleModal}
/>
</div>
)
}
const FirstModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={firstModalOpen}
onHide={() => props.toggleModal('first')}
>
First modal content...
</Modal>
)
}
const SecondModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={secondModalOpen}
onHide={() => props.toggleModal('second')}
>
Second modal content...
</Modal>
)
}
For the second case it would be as you have written in your example, the only optimisation you can do for the second case is to store an array of modal objects and render them dynamically or let each modal handle its own toggle states and use useImperativeHandle to provide methods which parent can call to child modals like
const App = () => {
const firstRef = useRef(null);
const secondRef = useRef(null);
return (
<div>
<button onClick={() => this.firstRef.current.toggleModal()}>Open First Modal</button>
<button onClick={() => this.secondRef.current.toggleModal()}>Open Second Modal</button>
<FirstModal
{...props}
ref={firstRef}
/>
<SecondModal
{...props}
ref={secondRef}
/>
</div>
)
}
const FirstModal = forwardRef((props, ref) => {
const { showModal, toggleModal } = useToggleModal(false, ref);
return (
<Modal
{ ...rest }
show={showModal}
onHide={toggleModal}
>
First modal content...
</Modal>
)
})
const SecondModal = forwardRef((props, ref) => {
const { showModal, toggleModal } = useToggleModal(false, ref);
return (
<Modal
{ ...props }
show={showModal}
onHide={toggleModal}
>
Second modal content...
</Modal>
)
})
// state hook attempt
const useToggleModal = (init, ref) => {
const [show, setToggleModal] = useState(init);
const toggleModal = () => setToggleModal(!show);
useImperativeHandle(ref, () => ({
toggleModal
}))
return { show, toggleModal };
};

Categories