From 4a928afa6e03d05253f652eb5cc27487ca705b9e Mon Sep 17 00:00:00 2001 From: fallintoplace <38443830+fallintoplace@users.noreply.github.com> Date: Sun, 4 Oct 2026 09:47:22 +0200 Subject: [PATCH] fix: memoize useEnterpriseConnect result --- __tests__/use-enterprise-connect.test.tsx | 87 ++++++++++++++++++++++- src/use-enterprise-connect.tsx | 11 +-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/__tests__/use-enterprise-connect.test.tsx b/__tests__/use-enterprise-connect.test.tsx index 33f9d834..f38302a8 100644 --- a/__tests__/use-enterprise-connect.test.tsx +++ b/__tests__/use-enterprise-connect.test.tsx @@ -1,9 +1,11 @@ +import React, { useEffect } from 'react'; import { renderHook, waitFor } from '@testing-library/react'; import { isFederatedDomain as spaIsFederatedDomain, Auth0Client, } from '@auth0/auth0-spa-js'; import useEnterpriseConnect from '../src/use-enterprise-connect'; +import { Auth0ContextInterface, initialContext } from '../src/auth0-context'; import { createWrapper } from './helpers'; jest.mock('@auth0/auth0-spa-js'); @@ -20,6 +22,87 @@ describe('useEnterpriseConnect', () => { }); }); + it('should keep the result stable when the provider initializes or the consumer re-renders', async () => { + const effect = jest.fn(); + const { result, rerender } = renderHook( + () => { + const enterpriseConnect = useEnterpriseConnect(); + useEffect(effect, [enterpriseConnect]); + return enterpriseConnect; + }, + { wrapper: createWrapper() } + ); + const initial = result.current; + + await waitFor(() => expect(clientMock.getUser).toHaveBeenCalled()); + for (let i = 0; i < 3; i++) { + rerender(); + } + + expect(effect).toHaveBeenCalledTimes(1); + expect(result.current).toBe(initial); + }); + + it.each(['getConfiguration', 'loginWithRedirect'] as const)( + 'should update the result when %s changes in a custom context', + async (method) => { + const getConfiguration = jest.fn(() => ({ + domain: 'first.example.com', + clientId: '__test_client_id__', + })); + const loginWithRedirect = jest.fn(); + let contextValue: Auth0ContextInterface = { + ...initialContext, + getConfiguration, + loginWithRedirect, + }; + const context = + React.createContext(initialContext); + const wrapper = ({ children }: React.PropsWithChildren) => ( + {children} + ); + const { result, rerender } = renderHook( + () => useEnterpriseConnect(context), + { wrapper } + ); + const initial = result.current; + const nextGetConfiguration = jest.fn(() => ({ + domain: 'second.example.com', + clientId: '__test_client_id__', + })); + const nextLoginWithRedirect = jest.fn(); + + contextValue = + method === 'getConfiguration' + ? { ...contextValue, getConfiguration: nextGetConfiguration } + : { ...contextValue, loginWithRedirect: nextLoginWithRedirect }; + rerender(); + + expect(result.current).not.toBe(initial); + expect( + result.current.isFederatedDomain === initial.isFederatedDomain + ).toBe(method !== 'getConfiguration'); + expect(result.current.loginWithSSO === initial.loginWithSSO).toBe( + method !== 'loginWithRedirect' + ); + + await result.current.isFederatedDomain('acme.com'); + expect(federatedMock).toHaveBeenCalledWith( + contextValue.getConfiguration().domain, + 'acme.com', + undefined + ); + await result.current.loginWithSSO('jane@acme.com'); + expect(contextValue.loginWithRedirect).toHaveBeenCalledWith({ + authorizationParams: { login_hint: 'jane@acme.com' }, + }); + + const updated = result.current; + rerender(); + expect(result.current).toBe(updated); + } + ); + it('calls isFederatedDomain with the configured domain and email domain', async () => { federatedMock.mockResolvedValueOnce(true); const wrapper = createWrapper(); @@ -51,9 +134,7 @@ describe('useEnterpriseConnect', () => { it('loginWithSSO calls loginWithRedirect with login_hint set from the email', async () => { const wrapper = createWrapper(); const { result } = renderHook(() => useEnterpriseConnect(), { wrapper }); - await waitFor(() => - expect(clientMock.loginWithRedirect).not.toBeNull() - ); + await waitFor(() => expect(clientMock.loginWithRedirect).not.toBeNull()); await result.current.loginWithSSO('jane@acme.com'); diff --git a/src/use-enterprise-connect.tsx b/src/use-enterprise-connect.tsx index 4b0e2496..6b80887e 100644 --- a/src/use-enterprise-connect.tsx +++ b/src/use-enterprise-connect.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import { isFederatedDomain as spaIsFederatedDomain, IsFederatedDomainOptions, @@ -43,9 +43,7 @@ export interface UseEnterpriseConnect { * only the email domain. `loginWithSSO` is sugar over `loginWithRedirect` * that sets `login_hint` to the provided email. */ -const useEnterpriseConnect = ( - context = Auth0Context -): UseEnterpriseConnect => { +const useEnterpriseConnect = (context = Auth0Context): UseEnterpriseConnect => { const { getConfiguration, loginWithRedirect } = useContext( context ) as Auth0ContextInterface; @@ -69,7 +67,10 @@ const useEnterpriseConnect = ( [loginWithRedirect] ); - return { isFederatedDomain, loginWithSSO }; + return useMemo( + () => ({ isFederatedDomain, loginWithSSO }), + [isFederatedDomain, loginWithSSO] + ); }; export default useEnterpriseConnect;