Skip to content

Commit 3aff9a8

Browse files
Feature/react19 (#824)
This change enables React 19 support for `auth0-react` This change builds upon PR #819 by @leefreemanxyz ### Changes - Version bumps of related packages in package.json, package-lock.json update. - Updated tests to use the new version of packages. - Added handling for an edge case where error was not thrown if popup was closed in `loginWithPopup`. - Updated sample code to use React 19. - Linting changes. #### Commits - **feat(react 19): update react to 19, testing libraries deps to latest and fix broken tests** - **chore: update @types/react and @types/react-dom to version 19.0.0** - **refactor: remove explicit return types for functional components in tests and providers** - **add explicit type annotations for React.JSX.Element** - **handle popup closed error not thrown** - **update example code** - **update tests** - **package json changes** ### Testing *PASSING* ```log =============================== Coverage summary =============================== Statements : 100% ( 166/166 ) Branches : 100% ( 72/72 ) Functions : 100% ( 41/41 ) Lines : 100% ( 158/158 ) ================================================================================ Test Suites: 8 passed, 8 total Tests: 81 passed, 81 total Snapshots: 0 total Time: 4.995 s Ran all test suites. ``` - [x] This change adds test coverage for new/changed/fixed functionality ### Checklist - [x] I have added documentation for new/changed functionality in this PR or in auth0.com/docs - [x] All active GitHub checks for tests, formatting, and security are passing - [x] The correct base branch is being used, if not the default branch
2 parents 6429fdd + c637fee commit 3aff9a8

14 files changed

+3603
-4889
lines changed

__tests__/auth-provider.test.tsx

Lines changed: 250 additions & 228 deletions
Large diffs are not rendered by default.

__tests__/helpers.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const createWrapper = ({
88
}: Partial<Auth0ProviderOptions> = {}) => {
99
return function Wrapper({
1010
children,
11-
}: PropsWithChildren<Record<string, unknown>>): JSX.Element {
11+
}: PropsWithChildren<Record<string, unknown>>): React.JSX.Element {
1212
return (
1313
<Auth0Provider domain={domain} clientId={clientId} {...opts}>
1414
{children}

__tests__/ssr.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('In a Node SSR environment', () => {
1313
ReactDOMServer.renderToString(
1414
<Auth0Provider clientId="__client_id__" domain="__domain__">
1515
<Auth0Context.Consumer>
16-
{(value): JSX.Element => {
16+
{(value): React.JSX.Element => {
1717
({ isLoading, isAuthenticated, user, loginWithRedirect } = value);
1818
return <div>App</div>;
1919
}}

__tests__/use-auth.test.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
import { act, renderHook, waitFor } from '@testing-library/react';
12
import React from 'react';
3+
import { Auth0ContextInterface, initialContext } from '../src/auth0-context';
24
import useAuth0 from '../src/use-auth0';
3-
import { act, renderHook } from '@testing-library/react-hooks';
45
import { createWrapper } from './helpers';
5-
import { Auth0ContextInterface, initialContext } from '../src/auth0-context';
66

77
describe('useAuth0', () => {
88
it('should provide the auth context', async () => {
99
const wrapper = createWrapper();
1010
const {
11-
result: { current },
12-
waitForNextUpdate,
11+
result: { current }
1312
} = renderHook(() => useAuth0(), { wrapper });
14-
await waitForNextUpdate();
15-
expect(current).toBeDefined();
13+
await waitFor(() => {
14+
expect(current).toBeDefined();
15+
});
1616
});
1717

1818
it('should throw with no provider', () => {
@@ -42,10 +42,10 @@ describe('useAuth0', () => {
4242
const wrapper = createWrapper({ context });
4343
const {
4444
result: { current },
45-
waitForNextUpdate,
4645
} = renderHook(() => useAuth0(context), { wrapper });
47-
await waitForNextUpdate();
48-
expect(current).toBeDefined();
49-
expect(current.loginWithRedirect).not.toThrowError();
46+
await waitFor(() => {
47+
expect(current).toBeDefined();
48+
expect(current.loginWithRedirect).not.toThrowError();
49+
});
5050
});
5151
});

__tests__/with-auth0.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import '@testing-library/jest-dom/extend-expect';
2-
import React, { Component } from 'react';
3-
import withAuth0, { WithAuth0Props } from '../src/with-auth0';
1+
import '@testing-library/jest-dom';
42
import { render, screen } from '@testing-library/react';
3+
import React, { Component } from 'react';
54
import { Auth0ContextInterface, initialContext } from '../src/auth0-context';
5+
import withAuth0, { WithAuth0Props } from '../src/with-auth0';
66

77
describe('withAuth0', () => {
88
it('should wrap a class component', () => {
99
class MyComponent extends Component<WithAuth0Props> {
10-
render(): JSX.Element {
10+
render() {
1111
return <>hasAuth: {`${!!this.props.auth0}`}</>;
1212
}
1313
}
@@ -19,7 +19,7 @@ describe('withAuth0', () => {
1919
it('should wrap a class component and provide context', () => {
2020
const context = React.createContext<Auth0ContextInterface>(initialContext);
2121
class MyComponent extends Component<WithAuth0Props> {
22-
render(): JSX.Element {
22+
render() {
2323
return <>hasAuth: {`${!!this.props.auth0}`}</>;
2424
}
2525
}

__tests__/with-authentication-required.test.tsx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import '@testing-library/jest-dom/extend-expect';
2-
import React from 'react';
3-
import withAuthenticationRequired from '../src/with-authentication-required';
4-
import { render, screen, waitFor, act } from '@testing-library/react';
51
import { Auth0Client, User } from '@auth0/auth0-spa-js';
6-
import Auth0Provider from '../src/auth0-provider';
2+
import '@testing-library/jest-dom';
3+
import { act, render, screen, waitFor } from '@testing-library/react';
4+
import React from 'react';
75
import { Auth0ContextInterface, initialContext } from '../src/auth0-context';
6+
import Auth0Provider from '../src/auth0-provider';
7+
import withAuthenticationRequired from '../src/with-authentication-required';
88
import { defer } from './helpers';
99

1010
const mockClient = jest.mocked(new Auth0Client({ clientId: '', domain: '' }));
1111

1212
describe('withAuthenticationRequired', () => {
1313
it('should block access to a private component when not authenticated', async () => {
1414
mockClient.getUser.mockResolvedValue(undefined);
15-
const MyComponent = (): JSX.Element => <>Private</>;
15+
const MyComponent = () => <>Private</>;
1616
const WrappedComponent = withAuthenticationRequired(MyComponent);
1717
render(
1818
<Auth0Provider clientId="__test_client_id__" domain="__test_domain__">
@@ -27,7 +27,7 @@ describe('withAuthenticationRequired', () => {
2727

2828
it('should allow access to a private component when authenticated', async () => {
2929
mockClient.getUser.mockResolvedValue({ name: '__test_user__' });
30-
const MyComponent = (): JSX.Element => <>Private</>;
30+
const MyComponent = () => <>Private</>;
3131
const WrappedComponent = withAuthenticationRequired(MyComponent);
3232
await act(() => {
3333
render(
@@ -49,8 +49,8 @@ describe('withAuthenticationRequired', () => {
4949
const deferred = defer<User | undefined>();
5050
mockClient.getUser.mockResolvedValue(deferred.promise);
5151

52-
const MyComponent = (): JSX.Element => <>Private</>;
53-
const OnRedirecting = (): JSX.Element => <>Redirecting</>;
52+
const MyComponent = () => <>Private</>;
53+
const OnRedirecting = () => <>Redirecting</>;
5454
const WrappedComponent = withAuthenticationRequired(MyComponent, {
5555
onRedirecting: OnRedirecting,
5656
});
@@ -84,7 +84,7 @@ describe('withAuthenticationRequired', () => {
8484
mockClient.loginWithRedirect.mockImplementationOnce(async () => {
8585
callOrder.push('loginWithRedirect');
8686
});
87-
const MyComponent = (): JSX.Element => <>Private</>;
87+
const MyComponent = () => <>Private</>;
8888
const OnBeforeAuthentication = jest
8989
.fn()
9090
.mockImplementationOnce(async () => {
@@ -112,7 +112,7 @@ describe('withAuthenticationRequired', () => {
112112

113113
it('should pass additional options on to loginWithRedirect', async () => {
114114
mockClient.getUser.mockResolvedValue(undefined);
115-
const MyComponent = (): JSX.Element => <>Private</>;
115+
const MyComponent = () => <>Private</>;
116116
const WrappedComponent = withAuthenticationRequired(MyComponent, {
117117
loginOptions: {
118118
fragment: 'foo',
@@ -134,7 +134,7 @@ describe('withAuthenticationRequired', () => {
134134

135135
it('should merge additional appState with the returnTo', async () => {
136136
mockClient.getUser.mockResolvedValue(undefined);
137-
const MyComponent = (): JSX.Element => <>Private</>;
137+
const MyComponent = () => <>Private</>;
138138
const WrappedComponent = withAuthenticationRequired(MyComponent, {
139139
loginOptions: {
140140
appState: {
@@ -162,7 +162,7 @@ describe('withAuthenticationRequired', () => {
162162

163163
it('should accept a returnTo function', async () => {
164164
mockClient.getUser.mockResolvedValue(undefined);
165-
const MyComponent = (): JSX.Element => <>Private</>;
165+
const MyComponent = () => <>Private</>;
166166
const WrappedComponent = withAuthenticationRequired(MyComponent, {
167167
returnTo: () => '/foo',
168168
});
@@ -184,9 +184,9 @@ describe('withAuthenticationRequired', () => {
184184

185185
it('should call loginWithRedirect only once even if parent state changes', async () => {
186186
mockClient.getUser.mockResolvedValue(undefined);
187-
const MyComponent = (): JSX.Element => <>Private</>;
187+
const MyComponent = () => <>Private</>;
188188
const WrappedComponent = withAuthenticationRequired(MyComponent);
189-
const App = ({ foo }: { foo: number }): JSX.Element => (
189+
const App = ({ foo }: { foo: number }) => (
190190
<div>
191191
{foo}
192192
<Auth0Provider clientId="__test_client_id__" domain="__test_domain__">
@@ -210,7 +210,7 @@ describe('withAuthenticationRequired', () => {
210210
mockClient.getUser.mockResolvedValueOnce({ name: '__test_user__' });
211211
mockClient.getUser.mockResolvedValueOnce(undefined);
212212
const context = React.createContext<Auth0ContextInterface>(initialContext);
213-
const MyComponent = (): JSX.Element => <>Private</>;
213+
const MyComponent = () => <>Private</>;
214214
const WrappedComponent = withAuthenticationRequired(MyComponent, {
215215
context,
216216
});
@@ -241,7 +241,7 @@ describe('withAuthenticationRequired', () => {
241241
mockClient.getUser.mockResolvedValueOnce(undefined);
242242
mockClient.getUser.mockResolvedValueOnce({ name: '__test_user__' });
243243
const context = React.createContext<Auth0ContextInterface>(initialContext);
244-
const MyComponent = (): JSX.Element => <>Private</>;
244+
const MyComponent = () => <>Private</>;
245245
const WrappedComponent = withAuthenticationRequired(MyComponent, {
246246
context,
247247
});

examples/cra-react-router/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@auth0/auth0-react": "file:../..",
6+
"@auth0/auth0-react": "2.2.4",
77
"@types/node": "^17.0.29",
8-
"@types/react": "file:../../node_modules/@types/react",
9-
"@types/react-dom": "file:../../node_modules/@types/react-dom",
10-
"react": "file:../../node_modules/react",
11-
"react-dom": "file:../../node_modules/react-dom",
8+
"@types/react": "18.3.18",
9+
"@types/react-dom": "18.3.5",
10+
"react": "18.3.1",
11+
"react-dom": "18.3.1",
1212
"react-router-dom": "^6.3.0",
1313
"react-scripts": "^5.0.1",
1414
"typescript": "^4.6.3"
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
1+
import { createRoot } from "react-dom/client";
12
import React, { PropsWithChildren } from 'react';
2-
import ReactDOM from 'react-dom';
33
import App from './App';
4-
import { Auth0Provider, AppState } from '@auth0/auth0-react';
4+
import { Auth0Provider, AppState, Auth0ContextInterface, User } from '@auth0/auth0-react';
55
import { BrowserRouter, useNavigate } from 'react-router-dom';
6-
import { Auth0ProviderOptions } from '../../../src';
6+
import { Auth0ProviderOptions } from '../../../src/index.js';
77

88
const Auth0ProviderWithRedirectCallback = ({
99
children,
10+
context,
1011
...props
11-
}: PropsWithChildren<Auth0ProviderOptions>) => {
12+
}: PropsWithChildren<Omit<Auth0ProviderOptions, 'context'>> & {
13+
context?: React.Context<Auth0ContextInterface<User>>
14+
}) => {
1215
const navigate = useNavigate();
1316

14-
const onRedirectCallback = (appState?: AppState) => {
15-
navigate((appState && appState.returnTo) || window.location.pathname);
17+
const onRedirectCallback = (appState?: AppState, user?: User) => {
18+
navigate((appState?.returnTo) || window.location.pathname);
1619
};
1720

1821
return (
19-
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
22+
<Auth0Provider
23+
onRedirectCallback={onRedirectCallback}
24+
context={context}
25+
{...props}
26+
>
2027
{children}
2128
</Auth0Provider>
2229
);
2330
};
31+
const root = createRoot(document.getElementById('root')!);
2432

25-
ReactDOM.render(
33+
root.render(
2634
<React.StrictMode>
2735
<BrowserRouter>
2836
<Auth0ProviderWithRedirectCallback
@@ -37,6 +45,5 @@ ReactDOM.render(
3745
<App />
3846
</Auth0ProviderWithRedirectCallback>
3947
</BrowserRouter>
40-
</React.StrictMode>,
41-
document.getElementById('root')
48+
</React.StrictMode>
4249
);

examples/cra-react-router/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"allowSyntheticDefaultImports": true,
99
"strict": true,
1010
"forceConsistentCasingInFileNames": true,
11-
"module": "esnext",
11+
"module": "NodeNext",
1212
"moduleResolution": "NodeNext",
1313
"resolveJsonModule": true,
1414
"isolatedModules": true,

0 commit comments

Comments
 (0)