UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

118 lines 5.04 kB
import { useCallback, useEffect, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import useAuthProvider, { defaultAuthParams } from "./useAuthProvider.js"; import { useResetStore } from "../store/index.js"; import { useBasename, useLocation, useNavigate } from "../routing/index.js"; import { removeDoubleSlashes } from "../routing/useCreatePath.js"; /** * Get a callback for calling the authProvider.logout() method, * redirect to the login page, and clear the store. * * @see useAuthProvider * * @returns {Function} logout callback * * @example * * import { useLogout } from 'react-admin'; * * const LogoutButton = () => { * const logout = useLogout(); * const handleClick = () => logout(); * return <button onClick={handleClick}>Logout</button>; * } */ const useLogout = () => { const authProvider = useAuthProvider(); const queryClient = useQueryClient(); const resetStore = useResetStore(); const navigate = useNavigate(); const location = useLocation(); const locationRef = useRef(location); const basename = useBasename(); const loginUrl = removeDoubleSlashes(`${basename}/${defaultAuthParams.loginUrl}`); /* * We need the current location to pass in the router state * so that the login hook knows where to redirect to as next route after login. * * But if we used the location from useLocation as a dependency of the logout * function, it would be rebuilt each time the user changes location. * Consequently, that would force a rerender of all components using this hook * upon navigation (CoreAdminRouter for example). * * To avoid that, we store the location in a ref. */ useEffect(() => { locationRef.current = location; }, [location]); const logout = useCallback((params = {}, redirectFromCaller, redirectToCurrentLocationAfterLogin = true) => { if (authProvider) { return authProvider.logout(params).then(redirectFromLogout => { if (redirectFromLogout === false || redirectFromCaller === false) { resetStore(); queryClient.clear(); // do not redirect return; } const finalRedirectTo = redirectFromCaller || redirectFromLogout || loginUrl; if (finalRedirectTo?.startsWith('http')) { // absolute link (e.g. https://my.oidc.server/login) resetStore(); queryClient.clear(); window.location.href = finalRedirectTo; return finalRedirectTo; } // redirectTo is an internal location that may contain a query string, e.g. '/login?foo=bar' // we must split it to pass a structured location to navigate() const redirectToParts = finalRedirectTo.split('?'); const newLocation = { pathname: redirectToParts[0], }; let newLocationOptions = {}; if (redirectToCurrentLocationAfterLogin && locationRef.current && locationRef.current.pathname) { newLocationOptions = { state: { nextPathname: locationRef.current.pathname, nextSearch: locationRef.current.search, }, }; } if (redirectToParts[1]) { newLocation.search = redirectToParts[1]; } // We need to navigate and reset the store after a litte delay to avoid a race condition // between the store reset and the navigation. // // This would only happen when the `authProvider.getPermissions` method returns // a resolved promise with no delay: If the store was reset before the navigation, // the `usePermissions` query would reset, causing the `CoreAdminRoutes` component to // rerender the `LogoutOnMount` component leading to an infinite loop. setTimeout(() => { navigate(newLocation, newLocationOptions); resetStore(); queryClient.clear(); }, 0); return redirectFromLogout; }); } else { navigate({ pathname: loginUrl, }, { state: { nextPathname: locationRef.current && locationRef.current.pathname, }, }); resetStore(); queryClient.clear(); return Promise.resolve(); } }, [authProvider, resetStore, loginUrl, queryClient, navigate]); return logout; }; export default useLogout; //# sourceMappingURL=useLogout.js.map