Last active
October 28, 2023 08:02
-
-
Save mxmtsk/cd01b0a3e43d21a51e054b1a92255092 to your computer and use it in GitHub Desktop.
React implementation of Vue3 Adapater for https://github.com/lepikhinb/momentum-modal
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
/** | |
* Unfortunately I didn't find a way to add it directly to createInertiaApp so | |
* currently I'm placing it in my BaseLayout which is called on any route anyway | |
*/ | |
import React from 'react'; | |
import { ModalProvider } from '../components/momentum-modal/ModalContext'; | |
const BaseLayout: React.FC = ({ children }) => { | |
/* Wrap your children with the provider */ | |
return <ModalProvider resolve={(name) => import(`../pages/${name}`)}>{children}</ModalProvider>; | |
}; | |
export default BaseLayout; |
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 React from 'react'; | |
import { Inertia, Page } from '@inertiajs/inertia'; | |
import { usePage } from '@inertiajs/inertia-react'; | |
import axios from 'axios'; | |
interface Modal { | |
component: string; | |
baseURL: string; | |
redirectURL: string | null; | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
props: Record<string, any>; | |
key: string; | |
nonce: string; | |
} | |
interface Context { | |
show: boolean; | |
close: (shouldRedirect: boolean) => void; | |
redirect: () => void; | |
} | |
const ModalContext = React.createContext({} as Context); | |
const ModalProvider: React.FC<{ resolve: (name: string) => Promise<{ default: React.FC }> }> = ({ | |
children, | |
resolve, | |
}) => { | |
const { props: pageProps } = usePage<Page<{ modal: Modal }>>(); | |
const modal = React.useMemo(() => pageProps.modal, [pageProps]); | |
const props = React.useMemo(() => (modal ? modal.props : {}), [modal]); | |
const key = React.useMemo(() => (modal ? modal.key : ''), [modal]); | |
const [componentName, setComponentName] = React.useState<null | string>(null); | |
const [Component, setComponent] = React.useState<boolean | React.FC>(false); | |
const [show, setShow] = React.useState(false); | |
const [nonce, setNonce] = React.useState<null | string>(null); | |
const setHeaders = React.useCallback((values: Record<string, string | null>) => { | |
Object.entries(values).forEach(([key, value]) => | |
['post', 'put', 'patch', 'delete'].forEach((method) => { | |
axios.defaults.headers[method][key] = value; | |
}) | |
); | |
}, []); | |
const resetHeaders = React.useCallback(() => { | |
const headers = ['X-Inertia-Modal-Key', 'X-Inertia-Modal-Redirect']; | |
headers.forEach((key) => | |
['get', 'post', 'put', 'patch', 'delete'].forEach((method) => { | |
delete axios.defaults.headers[method][key]; | |
}) | |
); | |
}, []); | |
const updateHeaders = React.useCallback(() => { | |
setHeaders({ | |
'X-Inertia-Modal-Key': key, | |
'X-Inertia-Modal-Redirect': modal.redirectURL, | |
}); | |
axios.defaults.headers.get['X-Inertia-Modal-Redirect'] = modal.redirectURL ?? ''; | |
}, [modal, key, setHeaders]); | |
const redirect = React.useCallback(() => { | |
const redirectURL = modal.redirectURL ?? modal.baseURL; | |
if (!redirectURL) { | |
return; | |
} | |
return Inertia.visit(redirectURL, { | |
preserveScroll: true, | |
}); | |
}, [modal]); | |
const close = React.useCallback( | |
(shouldRedirect = false) => { | |
setShow(false); | |
resetHeaders(); | |
if (shouldRedirect) { | |
redirect(); | |
} | |
}, | |
[resetHeaders, redirect] | |
); | |
const resolveComponent = React.useCallback(() => { | |
if (!modal || nonce === modal.nonce) { | |
return close(); | |
} | |
if (componentName !== modal.component) { | |
setComponentName(modal.component); | |
if (modal.component) { | |
resolve(modal.component) | |
.then((loadedComponent) => { | |
setComponent(() => loadedComponent.default); | |
}) | |
.catch((e) => { | |
console.log('error loading component:', e); | |
setComponent(false); | |
}); | |
} else { | |
setComponent(false); | |
} | |
} | |
setNonce(modal.nonce); | |
setShow(true); | |
}, [nonce, modal, close, componentName, resolve]); | |
React.useEffect(() => { | |
if (typeof window !== 'undefined') { | |
window.addEventListener('popstate', () => { | |
setNonce(null); | |
}); | |
} | |
}, []); | |
React.useEffect(() => { | |
if ((modal && modal.nonce !== nonce) || (nonce && !modal)) { | |
resolveComponent(); | |
} | |
}, [modal, resolveComponent, nonce]); | |
React.useEffect(() => { | |
if (key) { | |
updateHeaders(); | |
} | |
}, [key, updateHeaders]); | |
return ( | |
<ModalContext.Provider value={{ close, show, redirect }}> | |
{children} | |
{/* This shows the modal. could also be placed in a seperate component but in my project i just need it at the bottom of the page */} | |
{typeof Component !== 'boolean' && show ? <Component {...props} /> : null} | |
</ModalContext.Provider> | |
); | |
}; | |
const useModal = (): Context => React.useContext(ModalContext); | |
export { ModalProvider, useModal }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment