mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 10:55:37 +00:00
Merge pull request #14759 from strapi/enhancement/axios-refactoring
useFetchClient hook replacing axiosInstance
This commit is contained in:
commit
978630d655
@ -15,6 +15,7 @@ import Informations from '../index';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
useCMEditViewDataManager: jest.fn(),
|
||||
wrapAxiosInstance: jest.fn(() => {}),
|
||||
}));
|
||||
|
||||
const makeApp = () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
@ -33,4 +33,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
@ -10,3 +10,4 @@ export { default as usePermissionsDataManager } from './usePermissionsDataManage
|
||||
export { default as useReleaseNotification } from './useReleaseNotification';
|
||||
export { default as useThemeToggle } from './useThemeToggle';
|
||||
export { default as useRegenerate } from './useRegenerate';
|
||||
export { default as useFetchClient } from './useFetchClient';
|
||||
|
||||
23
packages/core/admin/admin/src/hooks/useFetchClient/index.js
Normal file
23
packages/core/admin/admin/src/hooks/useFetchClient/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { getFetchClient } from '../../utils/getFetchClient';
|
||||
|
||||
const useFetchClient = () => {
|
||||
const controller = useRef(null);
|
||||
|
||||
if (controller.current === null) {
|
||||
controller.current = new AbortController();
|
||||
}
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
controller.current.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const defaultOptions = {
|
||||
signal: controller.current.signal,
|
||||
};
|
||||
|
||||
return getFetchClient(defaultOptions);
|
||||
};
|
||||
|
||||
export default useFetchClient;
|
||||
@ -11,6 +11,7 @@ jest.mock('react-redux', () => ({
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
useTracking: jest.fn(() => ({ trackUsage: trackUsageMock })),
|
||||
wrapAxiosInstance: jest.fn(() => {}),
|
||||
}));
|
||||
|
||||
describe('Admin | pages | Admin | useTrackUsage', () => {
|
||||
|
||||
@ -34,27 +34,32 @@ const ModalForm = ({ queryName, onToggle }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const toggleNotification = useNotification();
|
||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||
const postMutation = useMutation((body) => axiosInstance.post('/admin/users', body), {
|
||||
async onSuccess({ data }) {
|
||||
setRegistrationToken(data.data.registrationToken);
|
||||
await queryClient.invalidateQueries(queryName);
|
||||
goNext();
|
||||
setIsSubmitting(false);
|
||||
const postMutation = useMutation(
|
||||
(body) => {
|
||||
return axiosInstance.post('/admin/users', body);
|
||||
},
|
||||
onError(err) {
|
||||
setIsSubmitting(false);
|
||||
{
|
||||
async onSuccess({ data }) {
|
||||
setRegistrationToken(data.data.registrationToken);
|
||||
await queryClient.invalidateQueries(queryName);
|
||||
goNext();
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
onError(err) {
|
||||
setIsSubmitting(false);
|
||||
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
||||
});
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
||||
});
|
||||
|
||||
throw err;
|
||||
},
|
||||
onSettled() {
|
||||
unlockApp();
|
||||
},
|
||||
});
|
||||
throw err;
|
||||
},
|
||||
onSettled() {
|
||||
unlockApp();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const headerTitle = formatMessage({
|
||||
id: 'Settings.permissions.users.create',
|
||||
|
||||
45
packages/core/admin/admin/src/utils/fetchClient.js
Normal file
45
packages/core/admin/admin/src/utils/fetchClient.js
Normal file
@ -0,0 +1,45 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
|
||||
export const reqInterceptor = async (config) => {
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${auth.getToken()}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
export const reqErrorInterceptor = (error) => {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
||||
export const resInterceptor = (response) => response;
|
||||
|
||||
export const resErrorInterceptor = (error) => {
|
||||
// whatever you want to do with the error
|
||||
if (error?.response?.status === 401) {
|
||||
auth.clearAppStorage();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
|
||||
export const addInterceptors = (instance) => {
|
||||
instance.interceptors.request.use(reqInterceptor, reqErrorInterceptor);
|
||||
|
||||
instance.interceptors.response.use(resInterceptor, resErrorInterceptor);
|
||||
};
|
||||
|
||||
export const fetchClient = ({ baseURL }) => {
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
});
|
||||
addInterceptors(instance);
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
export default fetchClient({ baseURL: process.env.STRAPI_ADMIN_BACKEND_URL });
|
||||
10
packages/core/admin/admin/src/utils/getFetchClient.js
Normal file
10
packages/core/admin/admin/src/utils/getFetchClient.js
Normal file
@ -0,0 +1,10 @@
|
||||
import instance from './fetchClient';
|
||||
|
||||
export const getFetchClient = (defaultOptions = {}) => {
|
||||
return {
|
||||
get: (url, config) => instance.get(url, { ...defaultOptions, ...config }),
|
||||
put: (url, data, config) => instance.put(url, data, { ...defaultOptions, ...config }),
|
||||
post: (url, data, config) => instance.post(url, data, { ...defaultOptions, ...config }),
|
||||
del: (url, config) => instance.delete(url, { ...defaultOptions, ...config }),
|
||||
};
|
||||
};
|
||||
100
packages/core/admin/admin/src/utils/tests/fetchClient.test.js
Normal file
100
packages/core/admin/admin/src/utils/tests/fetchClient.test.js
Normal file
@ -0,0 +1,100 @@
|
||||
import { AxiosError } from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import {
|
||||
reqInterceptor,
|
||||
reqErrorInterceptor,
|
||||
resInterceptor,
|
||||
resErrorInterceptor,
|
||||
fetchClient,
|
||||
addInterceptors,
|
||||
} from '../fetchClient';
|
||||
|
||||
const token = 'coolToken';
|
||||
auth.getToken = jest.fn().mockReturnValue(token);
|
||||
auth.clearAppStorage = jest.fn().mockReturnValue(token);
|
||||
|
||||
describe('ADMIN | utils | fetchClient', () => {
|
||||
describe('Test the interceptors', () => {
|
||||
it('API request should add authorization token to header', async () => {
|
||||
const apiInstance = fetchClient({
|
||||
baseUrl: 'http://strapi',
|
||||
});
|
||||
const result = await apiInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
|
||||
expect(result.headers.Authorization).toContain(`Bearer ${token}`);
|
||||
expect(result.headers.Accept).toBe('application/json');
|
||||
expect(apiInstance.interceptors.response.handlers[0].fulfilled('foo')).toBe('foo');
|
||||
});
|
||||
describe('Test the addInterceptor function', () => {
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
it('should add a response interceptor to the fetchClient instance', () => {
|
||||
const apiInstance = fetchClient({
|
||||
baseUrl: 'http://strapi-test',
|
||||
});
|
||||
const spyReq = jest.spyOn(apiInstance.interceptors.request, 'use');
|
||||
const spyRes = jest.spyOn(apiInstance.interceptors.response, 'use');
|
||||
addInterceptors(apiInstance);
|
||||
expect(spyReq).toHaveBeenCalled();
|
||||
expect(spyRes).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Test the interceptors callbacks', () => {
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: { reload: jest.fn() },
|
||||
});
|
||||
});
|
||||
afterAll(() => {
|
||||
Object.defineProperty(window, 'location', { configurable: true, value: window.location });
|
||||
});
|
||||
it('should return the config object passed with the correct headers the request interceptor callback on success', async () => {
|
||||
const configMock = {
|
||||
headers: {
|
||||
common: { Accept: 'application/json, text/plain, */*' },
|
||||
delete: {},
|
||||
get: {},
|
||||
head: {},
|
||||
post: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
put: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
patch: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
},
|
||||
method: 'get',
|
||||
url: '/test',
|
||||
};
|
||||
const configResponse = await reqInterceptor(configMock);
|
||||
expect(configResponse.headers.Authorization).toBe(`Bearer ${token}`);
|
||||
});
|
||||
});
|
||||
it('should throw an error when the request interceptor error callback is called', () => {
|
||||
expect(reqErrorInterceptor('test')).rejects.toBe('test');
|
||||
expect(reqErrorInterceptor(new Error('test error'))).rejects.toThrow(new Error('test error'));
|
||||
});
|
||||
it('should return the response when the result interceptor callback is called', () => {
|
||||
const response = {
|
||||
msg: 'I am a response',
|
||||
};
|
||||
expect(resInterceptor(response).msg).toBe('I am a response');
|
||||
});
|
||||
it('should trigger the auth clearAppStorage and the window.location.reload when the result interceptor error callback is called', () => {
|
||||
const error = new AxiosError('Unauthorized');
|
||||
error.config = {};
|
||||
error.request = {};
|
||||
error.response = {
|
||||
data: { data: null, error: [Object] },
|
||||
status: 401,
|
||||
statusText: 'Unauthorized',
|
||||
};
|
||||
jest.spyOn(window.location, 'reload');
|
||||
try {
|
||||
resErrorInterceptor(error);
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(401);
|
||||
expect(auth.clearAppStorage).toHaveBeenCalledTimes(1);
|
||||
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { getFetchClient } from '../getFetchClient';
|
||||
|
||||
const token = 'coolToken';
|
||||
auth.getToken = jest.fn().mockReturnValue(token);
|
||||
|
||||
describe('ADMIN | utils | getFetchClient', () => {
|
||||
it('should return the 4 HTTP methods to call GET, POST, PUT and DELETE apis', () => {
|
||||
const response = getFetchClient();
|
||||
expect(response).toHaveProperty('get');
|
||||
expect(response).toHaveProperty('post');
|
||||
expect(response).toHaveProperty('put');
|
||||
expect(response).toHaveProperty('del');
|
||||
});
|
||||
it('should contain the headers config values and the data when we try to reach an unknown API', async () => {
|
||||
const response = getFetchClient();
|
||||
try {
|
||||
await response.get('/test');
|
||||
} catch (err) {
|
||||
const { headers } = err.config;
|
||||
expect(headers.Authorization).toContain(`Bearer ${token}`);
|
||||
expect(headers.Accept).toBe('application/json');
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -108,6 +108,7 @@ const DataManagerProvider = ({
|
||||
{ data: reservedNames },
|
||||
] = await Promise.all(
|
||||
['components', 'content-types', 'reserved-names'].map((endPoint) => {
|
||||
// TODO: remember to pass also the pluginId when you use the new get, post, put, delete methods from getFetchClient
|
||||
return axiosInstance.get(endPoint);
|
||||
})
|
||||
);
|
||||
@ -265,7 +266,7 @@ const DataManagerProvider = ({
|
||||
|
||||
if (userConfirm) {
|
||||
lockAppWithAutoreload();
|
||||
|
||||
// TODO: remember to pass also the pluginId when you use the new get, post, put, delete methods from getFetchClient
|
||||
await axiosInstance.delete(requestURL);
|
||||
|
||||
// Make sure the server has restarted
|
||||
@ -315,7 +316,7 @@ const DataManagerProvider = ({
|
||||
}
|
||||
|
||||
lockAppWithAutoreload();
|
||||
|
||||
// TODO: remember to pass also the pluginId when you use the new get, post, put, delete methods from getFetchClient
|
||||
await axiosInstance.delete(requestURL);
|
||||
|
||||
// Make sure the server has restarted
|
||||
@ -349,6 +350,7 @@ const DataManagerProvider = ({
|
||||
lockAppWithAutoreload();
|
||||
|
||||
// Update the category
|
||||
// TODO: remember to pass also the pluginId when you use the new get, post, put, delete methods from getFetchClient
|
||||
await axiosInstance({ url: requestURL, method: 'PUT', data: body });
|
||||
|
||||
// Make sure the server has restarted
|
||||
@ -506,7 +508,7 @@ const DataManagerProvider = ({
|
||||
|
||||
// Lock the app
|
||||
lockAppWithAutoreload();
|
||||
|
||||
// TODO: remember to pass also the pluginId when you use the new get, post, put, delete methods from getFetchClient
|
||||
await axiosInstance({
|
||||
url: requestURL,
|
||||
method,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
// TODO: remember to pass also the pluginId when you use the new get, post, put, delete methods from getFetchClient
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
const instance = axios.create({
|
||||
@ -34,4 +35,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
@ -33,4 +33,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
@ -95,6 +95,7 @@ export { default as contentManagementUtilRemoveFieldsFromData } from './content-
|
||||
export { default as getFileExtension } from './utils/getFileExtension/getFileExtension';
|
||||
export * from './utils/stopPropagation';
|
||||
export { default as difference } from './utils/difference';
|
||||
export { default as wrapAxiosInstance } from './utils/wrapAxiosInstance';
|
||||
|
||||
export { default as request } from './utils/request';
|
||||
export { default as getAPIInnerErrors } from './utils/getAPIInnerErrors';
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
function wrapAxiosInstance(instance) {
|
||||
if (process.env.NODE_ENV !== 'development') return instance;
|
||||
const wrapper = {};
|
||||
['request', 'get', 'head', 'delete', 'options', 'post', 'put', 'patch', 'getUri'].forEach(
|
||||
(methodName) => {
|
||||
wrapper[methodName] = (...args) => {
|
||||
console.log(
|
||||
'Deprecation warning: Usage of "axiosInstance" utility is deprecated and will be removed in the next major release. Instead, use the useFetchClient() hook, which is exported from the admin: { useFetchClient } from "@strapi/helper-plugin"'
|
||||
);
|
||||
|
||||
return instance[methodName](...args);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
export default wrapAxiosInstance;
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
@ -37,4 +37,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
@ -37,4 +37,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
@ -33,4 +33,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useCallback, useReducer, useEffect, useRef } from 'react';
|
||||
import { useNotification } from '@strapi/helper-plugin';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import axiosIntance from '../../utils/axiosInstance';
|
||||
import axiosInstance from '../../utils/axiosInstance';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const useFetchRole = (id) => {
|
||||
@ -29,7 +29,7 @@ const useFetchRole = (id) => {
|
||||
try {
|
||||
const {
|
||||
data: { role },
|
||||
} = await axiosIntance.get(`/${pluginId}/roles/${roleId}`);
|
||||
} = await axiosInstance.get(`/${pluginId}/roles/${roleId}`);
|
||||
|
||||
// Prevent updating state on an unmounted component
|
||||
if (isMounted.current) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
@ -33,4 +33,6 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
const wrapper = wrapAxiosInstance(instance);
|
||||
|
||||
export default wrapper;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user