diff --git a/src/AuthProvider.tsx b/src/AuthProvider.tsx index dc98709d..fafed05c 100644 --- a/src/AuthProvider.tsx +++ b/src/AuthProvider.tsx @@ -1,11 +1,4 @@ -import React, { - useCallback, - useEffect, - useMemo, - useReducer, - useRef, - useState, -} from "react"; +import React from "react"; import { UserManager, type UserManagerSettings, User } from "oidc-client-ts"; import type { SignoutRedirectArgs, @@ -156,15 +149,15 @@ export const AuthProvider = (props: AuthProviderProps): JSX.Element => { ...userManagerSettings } = props; - const [userManager] = useState(() => { + const [userManager] = React.useState(() => { return userManagerProp ?? (UserManagerImpl ? new UserManagerImpl(userManagerSettings as UserManagerSettings) : ({ settings: userManagerSettings } as UserManager)); }); - const [state, dispatch] = useReducer(reducer, initialAuthState); - const userManagerContext = useMemo( + const [state, dispatch] = React.useReducer(reducer, initialAuthState); + const userManagerContext = React.useMemo( () => Object.assign( { @@ -202,9 +195,9 @@ export const AuthProvider = (props: AuthProviderProps): JSX.Element => { ), [userManager], ); - const didInitialize = useRef(false); + const didInitialize = React.useRef(false); - useEffect(() => { + React.useEffect(() => { if (!userManager || didInitialize.current) { return; } @@ -227,7 +220,7 @@ export const AuthProvider = (props: AuthProviderProps): JSX.Element => { }, [userManager, skipSigninCallback, onSigninCallback]); // register to userManager events - useEffect(() => { + React.useEffect(() => { if (!userManager) return undefined; // event UserLoaded (e.g. initial load, silent renew success) const handleUserLoaded = (user: User) => { @@ -254,42 +247,44 @@ export const AuthProvider = (props: AuthProviderProps): JSX.Element => { }; }, [userManager]); - const removeUser = useCallback( + const removeUser = React.useCallback( userManager ? () => userManager.removeUser().then(onRemoveUser) : unsupportedEnvironment("removeUser"), [userManager, onRemoveUser], ); - const signoutRedirect = useCallback( + const signoutRedirect = React.useCallback( (args?: SignoutRedirectArgs) => userManagerContext.signoutRedirect(args).then(onSignoutRedirect), [userManagerContext.signoutRedirect, onSignoutRedirect], ); - const signoutPopup = useCallback( + const signoutPopup = React.useCallback( (args?: SignoutPopupArgs) => userManagerContext.signoutPopup(args).then(onSignoutPopup), [userManagerContext.signoutPopup, onSignoutPopup], ); - const signoutSilent = useCallback( + const signoutSilent = React.useCallback( (args?: SignoutSilentArgs) => userManagerContext.signoutSilent(args), [userManagerContext.signoutSilent], ); + const contextValue = React.useMemo(() => { + return { + ...state, + ...userManagerContext, + removeUser, + signoutRedirect, + signoutPopup, + signoutSilent, + }; + }, [state, userManagerContext, removeUser]); + return ( - + {children} ); diff --git a/test/AuthProvider.test.tsx b/test/AuthProvider.test.tsx index e4ae0bba..e0c83727 100644 --- a/test/AuthProvider.test.tsx +++ b/test/AuthProvider.test.tsx @@ -398,4 +398,22 @@ describe("AuthProvider", () => { mockSigninPopup.mockRestore(); }); + it("should not update context value after rerender without state changes", async () => { + // arrange + const wrapper = createWrapper({ ...settingsStub }); + const { result, rerender } = await act(async () => { + const { result, rerender } = renderHook(() => useAuth(), { + wrapper, + }); + return { result, rerender }; + }); + const memoized = result.current; + + // act + rerender(); + + // assert + expect(result.current).toBe(memoized); + }); + });