Last active
January 23, 2023 05:26
-
-
Save nicksheffield/e0f795a2f4bfe30695e26061d3da7319 to your computer and use it in GitHub Desktop.
react imperative modal paradigm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react' | |
// the shape of a modal definition. A unique id and an element | |
interface ModalDefinition { | |
id: string | |
content: React.ReactNode | |
} | |
type AddModalFn = (def: ModalDefinition) => void | |
type RemoveModalFn = (id: string) => void | |
// this object is essentially used to globally access the "setModals" function from the ModalProvider. | |
// the big assumption we have to make here is that there is one ModalProvider and one react application in this codebase... | |
// this is probably fine. | |
const globalContainer: { addModal: AddModalFn; removeModal: RemoveModalFn } = { | |
addModal: () => {}, | |
removeModal: () => {} | |
} | |
// this function is exported, and is used throughout our app to show modals | |
export const modal = (getContent: (close: () => void) => React.ReactNode) => { | |
const id = crypto.randomUUID() | |
const close = () => globalContainer.removeModal(id) | |
globalContainer.addModal({ | |
id, | |
content: getContent(close) | |
}) | |
} | |
export const ModalProvider = ({ children }) => { | |
// the list of modals is housed as react state in the provider | |
const [modals, setModals] = React.useState<ModalDefinition[]>([]) | |
// upon mounting of the provider | |
React.useEffect(() => { | |
// we redefine the globalContainer methods to add and remove from the providers state | |
globalContainer.addModal = (def) => setModals(list => [...list, def]) | |
globalContainer.removeModal = (id) => setModals(list => list.filter(x => x.id !== id)) | |
}, []) | |
// below we render the app, and then our list of modals | |
return ( | |
<> | |
{children} | |
<div id="modals"> | |
{modals.map(modal => ( | |
<div key={modal.id} className="modal">{modal.content}</div> | |
))} | |
</div> | |
</> | |
) | |
} | |
/// example usage: | |
const openMyModal = () => { | |
modal((close) => ( | |
<div> | |
<div>My Modal</div> | |
<div> | |
<button onClick={() => close()}>Close</button> | |
</div> | |
</div> | |
)) | |
} | |
<button onClick={() => openMyModal()}>Open Modal</button> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment