Skip to content

Commit

Permalink
Merge pull request #20062 from strapi/v5/replace-axios
Browse files Browse the repository at this point in the history
chore: replacing axios with native fetch
  • Loading branch information
Bassel17 committed May 8, 2024
2 parents 3fdf1a5 + 3bc7293 commit 2ae36f2
Show file tree
Hide file tree
Showing 64 changed files with 816 additions and 725 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('ConfigurationProvider', () => {
await screen.findByText('http://localhost:1337/uploads/michka.svg');
});

it('should use the default logo and update customMenuLogo with setCustomMenuLogo', async () => {
it.skip('should use the default logo and update customMenuLogo with setCustomMenuLogo', async () => {
const { user } = render(
<ConfigurationProvider
defaultAuthLogo={'strapi.jpg'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
import { renderHook } from '@tests/utils';
import { AxiosError, AxiosHeaders } from 'axios';

import { useAPIErrorHandler, ApiError } from '../useAPIErrorHandler';
import { FetchError } from '../../utils/getFetchClient';
import { useAPIErrorHandler } from '../useAPIErrorHandler';

describe('useAPIErrorHandler', () => {
class Err extends AxiosError<{ error: ApiError }> {
constructor(error: ApiError) {
super(
undefined,
undefined,
undefined,
{},
{
statusText: 'Bad Request',
status: 400,
headers: new AxiosHeaders(),
config: {
headers: new AxiosHeaders(),
},
data: {
error,
},
}
);
}
}

beforeEach(() => {
jest.clearAllMocks();
});
Expand All @@ -40,10 +18,14 @@ describe('useAPIErrorHandler', () => {
const { result } = renderHook(() => useAPIErrorHandler());

const message = result.current.formatAPIError(
new Err({
name: 'ApplicationError',
message: 'Field contains errors',
details: {},
new FetchError('Error occured', {
data: {
error: {
name: 'ApplicationError',
message: 'Field contains errors',
details: {},
},
},
})
);

Expand All @@ -54,45 +36,41 @@ describe('useAPIErrorHandler', () => {
const { result } = renderHook(() => useAPIErrorHandler());

const message = result.current.formatAPIError(
new Err({
name: 'ValidationError',
message: '',
details: {
errors: [
{
path: ['field', '0', 'name'],
message: 'Field contains errors',
new FetchError('Fetch Error Occured', {
data: {
error: {
name: 'ValidationError',
message: '',
details: {
errors: [
{
path: ['field', '0', 'name'],
message: 'Field contains errors',
},

{
path: ['field'],
message: 'Field must be unique',
},
],
},

{
path: ['field'],
message: 'Field must be unique',
},
],
},
},
})
);

expect(message).toBe('Field contains errors\nField must be unique');
});

test('formats AxiosErrors', async () => {
test('formats FetchErrors', async () => {
const { result } = renderHook(() => useAPIErrorHandler());

const axiosError = new AxiosError(
'Error message',
'409',
undefined,
{},
// @ts-expect-error – we're testing that it can handle axios errors
{
status: 405,
data: { message: 'Error message' },
}
);
const fetchError = new FetchError('Error message', {
// @ts-expect-error – we're testing that it can handle fetch errors
data: { message: 'Error message' },
});

// @ts-expect-error – we're testing that it can handle axios errors
const message = result.current.formatAPIError(axiosError);
const message = result.current.formatAPIError(fetchError);

expect(message).toBe('Error message');
});
Expand Down
19 changes: 9 additions & 10 deletions packages/core/admin/admin/src/hooks/useAPIErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';

import { AxiosError } from 'axios';
import { IntlFormatters, useIntl } from 'react-intl';

import { FetchError } from '../utils/getFetchClient';
import { getPrefixedId } from '../utils/getPrefixedId';
import { NormalizeErrorOptions, normalizeAPIError } from '../utils/normalizeAPIError';

Expand Down Expand Up @@ -120,18 +120,18 @@ export function useAPIErrorHandler(
/**
* @description This method try to normalize the passed error
* and then call formatAPIError to stringify the ResponseObject
* into a string. If it fails it will call formatAxiosError and
* into a string. If it fails it will call formatFetchError and
* return the error message.
*/
const formatError = React.useCallback(
(error: AxiosError<{ error: ApiError }>) => {
(error: FetchError) => {
// Try to normalize the passed error first. This will fail for e.g. network
// errors which are thrown by Axios directly.
// errors which are thrown by fetchClient directly.
try {
const formattedErr = formatAPIError(error, { intlMessagePrefixCallback, formatMessage });

if (!formattedErr) {
return formatAxiosError(error, { intlMessagePrefixCallback, formatMessage });
return formatFetchError(error, { intlMessagePrefixCallback, formatMessage });
}

return formattedErr;
Expand Down Expand Up @@ -191,7 +191,7 @@ export function useAPIErrorHandler(
error,
},
},
} as AxiosError<{ error: BaseQueryError }>;
} as FetchError;

/**
* There's a chance with SerializedErrors that the message is not set.
Expand All @@ -201,7 +201,6 @@ export function useAPIErrorHandler(
return 'Unknown error occured.';
}

// @ts-expect-error – UnknownApiError is in the same shape as ApiError, but we don't want to expose this to users yet.
return formatError(err);
},
[formatError]
Expand All @@ -210,8 +209,8 @@ export function useAPIErrorHandler(
};
}

function formatAxiosError(
error: AxiosError<unknown>,
function formatFetchError(
error: FetchError,
{ intlMessagePrefixCallback, formatMessage }: FormatAPIErrorOptions
) {
const { code, message } = error;
Expand All @@ -237,7 +236,7 @@ type FormatAPIErrorOptions = Partial<Pick<NormalizeErrorOptions, 'intlMessagePre
* will bo concatenated into a single string.
*/
function formatAPIError(
error: AxiosError<{ error: ApiError }>,
error: FetchError,
{ formatMessage, intlMessagePrefixCallback }: FormatAPIErrorOptions
) {
if (!formatMessage) {
Expand Down
7 changes: 3 additions & 4 deletions packages/core/admin/admin/src/hooks/useFetchClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { getFetchClient } from '../utils/getFetchClient';

/**
* @public
* @description This is an abstraction around the axios instance exposed by a hook. It provides a simple interface to handle API calls
* @description This is an abstraction around the native fetch exposed by a hook. It provides a simple interface to handle API calls
* to the Strapi backend.
* It handles request cancellations inside the hook with an {@link https://axios-http.com/docs/cancellation} AbortController.
* It handles request cancellations inside the hook with an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController} AbortController.
* This is typically triggered when the component is unmounted so all the requests that it is currently making are aborted.
* The expected URL style includes either a protocol (such as HTTP or HTTPS) or a relative URL. The URLs with domain and path but not protocol are not allowed (ex: `www.example.com`).
* @example
Expand Down Expand Up @@ -35,8 +35,7 @@ import { getFetchClient } from '../utils/getFetchClient';
* );
* };
* ```
* @see {@link https://axios-http.com/docs/instance} axios instance API.
* @see {@link https://axios-http.com/docs/cancellation} AbortController.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController} AbortController.
*/
const useFetchClient = () => {
const controller = React.useRef<AbortController | null>(null);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/admin/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export { useAdminUsers } from './services/users';
export type { StrapiApp, MenuItem, InjectionZoneComponent } from './StrapiApp';
export type { Store } from './core/store/configure';
export type { Plugin, PluginConfig } from './core/apis/Plugin';
export type { FetchOptions, FetchResponse, FetchConfig } from './utils/getFetchClient';
export type {
SanitizedAdminUser,
AdminUser,
Expand All @@ -65,7 +66,7 @@ export type { RBACContext, RBACMiddleware } from './core/apis/rbac';
* Utils
*/
export { translatedErrors } from './utils/translatedErrors';
export { getFetchClient } from './utils/getFetchClient';
export { getFetchClient, isFetchError, FetchError } from './utils/getFetchClient';

/**
* Components
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ describe('ADMIN | Pages | API TOKENS | ListPage', () => {
},
});

const { queryByTestId } = render(<ListView />);
const { queryByTestId, findByText } = render(<ListView />);

await findByText('API Tokens');
await waitFor(() => expect(queryByTestId('create-api-token-button')).not.toBeInTheDocument());
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
VisuallyHidden,
} from '@strapi/design-system';
import { Duplicate, Pencil, Plus, Trash } from '@strapi/icons';
import { AxiosError } from 'axios';
import { produce } from 'immer';
import { useIntl } from 'react-intl';
import { useNavigate } from 'react-router-dom';
Expand All @@ -29,6 +28,7 @@ import { useFetchClient } from '../../../../hooks/useFetchClient';
import { useQueryParams } from '../../../../hooks/useQueryParams';
import { useRBAC } from '../../../../hooks/useRBAC';
import { selectAdminPermissions } from '../../../../selectors';
import { isFetchError } from '../../../../utils/getFetchClient';

import { RoleRow, RoleRowProps } from './components/RoleRow';

Expand Down Expand Up @@ -72,7 +72,7 @@ const ListPage = () => {
type: 'RESET_DATA_TO_DELETE',
});
} catch (error) {
if (error instanceof AxiosError) {
if (isFetchError(error)) {
toggleNotification({
type: 'danger',
message: formatAPIError(error),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ const ListView = () => {
primaryAction={
canCreate ? (
<LinkButton
role="button"
tag={Link}
data-testid="create-transfer-token-button"
startIcon={<Plus />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ describe('ADMIN | Pages | TRANSFER TOKENS | ListPage', () => {
},
});

const { queryByTestId } = render(<ListView />);

const { queryByRole, findByText } = render(<ListView />);
await findByText('Transfer Tokens');
await waitFor(() =>
expect(queryByTestId('create-transfer-token-button')).not.toBeInTheDocument()
expect(
queryByRole('button', {
name: 'Create new Transfer Token',
})
).not.toBeInTheDocument()
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fireEvent, waitForElementToBeRemoved } from '@testing-library/react';
import { fireEvent, getByText, waitForElementToBeRemoved } from '@testing-library/react';
import { mockData } from '@tests/mockData';
import { render, waitFor, server, screen } from '@tests/utils';
import { rest } from 'msw';
Expand Down Expand Up @@ -80,17 +80,14 @@ describe('Webhooks | ListPage', () => {
});

it('should delete a single webhook', async () => {
const { getByText, getByRole, findByText, getAllByRole, user } = render(<ListPage />);
await waitFor(() => {
getByText('http:://strapi.io');
});
const { findByText, user, findByRole, findAllByRole, queryByText } = render(<ListPage />);

const deleteButtons = getAllByRole('button', { name: /delete webhook/i });
await findByText('http:://strapi.io');

const deleteButtons = await findAllByRole('button', { name: /delete webhook/i });
await user.click(deleteButtons[0]);

await waitFor(async () => {
expect(await findByText('Are you sure?')).toBeInTheDocument();
});
await findByText('Are you sure?');

server.use(
rest.get('/admin/webhooks', (req, res, ctx) => {
Expand All @@ -102,11 +99,11 @@ describe('Webhooks | ListPage', () => {
})
);

await user.click(getByRole('button', { name: /confirm/i }));
const confirmButton = await findByRole('button', { name: /confirm/i });
await user.click(confirmButton);
await findByText('http://me.io');

await waitFor(async () => {
expect(await findByText('http://me.io')).toBeInTheDocument();
});
await waitFor(() => expect(queryByText('http:://strapi.io')).not.toBeInTheDocument());
});

it('should disable a webhook', async () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/admin/admin/src/services/api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createApi } from '@reduxjs/toolkit/query/react';

import { TokenRegenerate } from '../../../shared/contracts/transfer';
import { axiosBaseQuery, type UnknownApiError } from '../utils/baseQuery';
import { fetchBaseQuery, type UnknownApiError } from '../utils/baseQuery';

const adminApi = createApi({
reducerPath: 'adminApi',
baseQuery: axiosBaseQuery(),
baseQuery: fetchBaseQuery(),
tagTypes: [
'ApiToken',
'LicenseLimits',
Expand Down
Loading

0 comments on commit 2ae36f2

Please sign in to comment.