Skip to content

Instantly share code, notes, and snippets.

@mxmtsk
Last active October 28, 2023 08:02
Show Gist options
  • Save mxmtsk/cd01b0a3e43d21a51e054b1a92255092 to your computer and use it in GitHub Desktop.
Save mxmtsk/cd01b0a3e43d21a51e054b1a92255092 to your computer and use it in GitHub Desktop.
React implementation of Vue3 Adapater for https://github.com/lepikhinb/momentum-modal
/**
* 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;
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