Skip to content

Commit

Permalink
Handle request errors on useUser hook [SDK-3227] (#639)
Browse files Browse the repository at this point in the history
  • Loading branch information
Widcket authored Apr 19, 2022
1 parent f5c920c commit e70112e
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 96 deletions.
9 changes: 8 additions & 1 deletion src/frontend/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export { default as ConfigProvider, ConfigProviderProps, useConfig } from './use-config';
export { default as UserProvider, UserProviderProps, UserProfile, UserContext, useUser } from './use-user';
export {
default as UserProvider,
UserProviderProps,
UserProfile,
UserContext,
RequestError,
useUser
} from './use-user';
export {
default as withPageAuthRequired,
WithPageAuthRequired,
Expand Down
41 changes: 36 additions & 5 deletions src/frontend/use-user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ export type UserContext = {
checkSession: () => Promise<void>;
};

/**
* The error thrown by the user fetcher.
*
* The `status` property contains the status code of the response. It is `0` when the request fails, e.g. due to being
* offline.
*
* This error is not thrown when the status code of the response is `401`, because that means the user is not
* authenticated.
*
* @category Client
*/
export class RequestError extends Error {
public status: number;

/* istanbul ignore next */
constructor(status: number) {
super();
this.status = status;
Object.setPrototypeOf(this, RequestError.prototype);
}
}

/**
* @ignore
*/
Expand Down Expand Up @@ -142,8 +164,18 @@ type UserProviderState = {
* @ignore
*/
const userFetcher: UserFetcher = async (url) => {
const response = await fetch(url);
return response.ok ? response.json() : undefined;
let response;
try {
response = await fetch(url);
} catch {
throw new RequestError(0); // Network error
}
if (response.ok) {
return response.json();
} else if (response.status === 401) {
return undefined;
}
throw new RequestError(response.status);
};

export default ({
Expand All @@ -159,9 +191,8 @@ export default ({
try {
const user = await fetcher(profileUrl);
setState((previous) => ({ ...previous, user, error: undefined }));
} catch (_e) {
const error = new Error(`The request to ${profileUrl} failed`);
setState((previous) => ({ ...previous, user: undefined, error }));
} catch (error) {
setState((previous) => ({ ...previous, error: error as Error }));
}
}, [profileUrl]);

Expand Down
1 change: 1 addition & 0 deletions src/index.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
UserProviderProps,
UserProfile,
UserContext,
RequestError,
useUser,
withPageAuthRequired,
WithPageAuthRequired
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export {
UserProviderProps,
UserProfile,
UserContext,
RequestError,
useUser,
WithPageAuthRequiredProps
} from './frontend';
Expand Down
24 changes: 19 additions & 5 deletions tests/fixtures/frontend.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';

import { UserProvider, UserProviderProps, UserProfile } from '../../src';
import { ConfigProvider, ConfigProviderProps } from '../../src/frontend';
import { ConfigProvider, ConfigProviderProps, RequestError } from '../../src/frontend';

type FetchUserMock = {
ok: boolean;
json?: () => Promise<UserProfile>;
status: number;
json?: () => Promise<UserProfile | undefined>;
};

export const user: UserProfile = {
Expand All @@ -32,17 +33,30 @@ export const withUserProvider = ({
export const fetchUserMock = (): Promise<FetchUserMock> => {
return Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve(user)
});
};

export const fetchUserUnsuccessfulMock = (): Promise<FetchUserMock> => {
export const fetchUserUnauthorizedMock = (): Promise<FetchUserMock> => {
return Promise.resolve({
ok: false
ok: false,
status: 401,
json: () => Promise.resolve(undefined)
});
};

export const fetchUserErrorMock = (): Promise<FetchUserMock> => Promise.reject(new Error('Error'));
export const fetchUserErrorMock = (): Promise<FetchUserMock> => {
return Promise.resolve({
ok: false,
status: 500,
json: () => Promise.resolve(undefined)
});
};

export const fetchUserNetworkErrorMock = (): Promise<FetchUserMock> => {
return Promise.reject(new RequestError(0));
};

export const withConfigProvider = ({ loginUrl }: ConfigProviderProps = {}): React.ComponentType => {
return (props: any): React.ReactElement => <ConfigProvider {...props} loginUrl={loginUrl} />;
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/test-app/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
Expand Down
Loading

0 comments on commit e70112e

Please sign in to comment.