Put error response interceptor on redux-axios-middleware - reactjs

I have a problem with https://github.com/svrcekmichal/redux-axios-middleware.
I want to set the interceptor response (error). But can't successfully set it up.
Here is my code:
function interceptorResponse({ dispatch, getState, getAction }, response) {
console.log(response);
}
export const client = axios.create({
baseURL: API_URL,
headers: {
Accept: 'application/json',
},
});
export const clientOptions = {
interceptors: {
request: [interceptorRequest],
response: [interceptorResponse],
},
};
the console.log(response) only respond if the response is 200. How can I set it to accept an error response?
I've tried set it like this
function interceptorResponse({ dispatch, getState, getAction }) {
return response => response.data, (error) => {
const meta = error.response.data.meta;
const { code, status } = meta;
console.log(meta);
};
}
but still never show anything.
Any soluion?

Here is an example usage with ES6 :
import axios from 'axios'
import axiosMiddleware from 'redux-axios-middleware'
const options = {
// not required, but use-full configuration option
returnRejectedPromiseOnError: true,
interceptors: {
request: [
({ getState, dispatch }, config) => {
// Request interception
return config
}
],
response: [
{
success: ({ dispatch }, response) => {
// Response interception
return response
},
error: ({ dispatch }, error) => {
// Response Error Interception
return Promise.reject(error)
}
}
]
}
}
export default axiosMiddleware(axios, options)
Note that the created middleware should be passed to createStore()

Related

ReactJs how to add interceptor in axios

I've been working on this for hours, and I have no idea where did it go wrong.
I want to have an axios interceptor for my ReactJs
this is my interceptor axiosHandler.js
import axios from "axios";
const axiosHandler = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
headers: {
Accept: "application/json",
},
});
axiosHandler.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
//axiosHandler.interceptors.response
export default axiosHandler;
And here is how I use the handler in my other component
import axiosHandler from "../services/axiosHandler";
const getData = async () => {
await axiosHandler
.get(`/path`)
.then((response) => {
//do something
})
};
And I get an error of below
services_axiosHandler__WEBPACK_IMPORTED_MODULE_0_.get is not a function
I've read many other solutions, but I can't find the difference as how it leads to the error of mine.
Where do I put it wrong?
Thank you
inside axios.index
import axios from "axios";
import { API_URL } from "../config/config";
const axiosHttp = axios.create({
baseURL: `${API_URL}`,
});
axiosHttp.interceptors.request.use(
(config) => {
const token = "Your Token here"
return {
...config,
headers: {
...(token !== null && { Authorization: `${token}` }),
...config.headers,
},
};
},
(error) => {
return Promise.reject(error);
}
);
axiosHttp.interceptors.response.use(
(response) => {
//const url = response.config.url;
//setLocalStorageToken(token);
return response;
},
(error) => {
if (error.response.status === 401) {
//(`unauthorized :)`);
//localStorage.removeItem("persist:root");
//removeLocalStorageToken
//window.location.href = "/login";
}
return Promise.reject(error);
}
);
export default axiosHttp;
Then inside your API function use it like below
import axiosHttp from "./utils/axios";
const getData = async ()=>{
try{
const response = await axiosHttp.get('/path')
return resposne;
}
catch(error){
//handle error here...
}
}
Last but not least, you shouldn't use await when using callback (then/catch)

axios returns "fullfilled" promise

Can someone please tell me why my Axios service function returns this:
The object looks fine in the service function.
Promise {<fulfilled>: {…}}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
Here is the call:
useEffect(() => {
setData(playlistService.getPlaylists);
}, []);
console.log(data);
And the function:
const config = {
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
};
const getPlaylists = async () => {
try {
const res = await axios.get(`${API_SONGS_URL}/`, config);
console.log('RES ', res);
return res;
} catch (error) {
if (error.response) {
return error.response.data;
}
}
};
this could work
useEffect(() => {
const fetchPlayListAsync = async () => {
const response = await playlistService.getPlaylists();
setData(response)
}
fetchPlayListAsync()
}, []);
you can add appropriate checks of fetched data

Redux Saga call api before token is set

I m trying to implements a react application with authentification using keycloak, all sounds good but when I refresh the page and there is fetching of an api, Saga perform the call before the token is set
there is my saga call
function* getAPI(action) {
const state = yield select();
try {
let response = yield call(
axiosRequest,
"get",
BaseURL,
`/foo/mini`,
{},
setAuthorizationBearer(state.auth.token),
{ sendToken: true },
"application/json"
);
yield put({ type: `${action.type}_SUCCESS`, payload: response, metadata: action.metadata })
} catch (e) {
yield put({ type: `${action.type}_ERROR`, payload: e })
}
}
and here is my axios request instance
import axios from "axios";
let authorizationBearer = null;
export const setAuthorizationBearer = token => {
authorizationBearer = token;
};
const instance = (
method,
baseURL = process.env.REACT_APP_ENDPOINT,
url,
data = null,
headers = null,
sendToken = true,
contentType
) => {
return new Promise((resolve, reject) => {
const p = {
sendToken: sendToken.sendToken,
data: {
...data,
},
};
const req = axios.create({
method,
baseURL,
url,
timeout: 30000,
headers: headers,
crossDomain: true,
});
headers = {};
if (p.sendToken && authorizationBearer) {
headers.Authorization = `Bearer ${authorizationBearer}`;
headers["Content-Type"] = contentType;
}
req({
method,
baseURL,
url,
data,
headers,
sendToken,
})
.then((payload) => {
if (payload) {
if (payload.status < 400) {
resolve(payload);
} else {
reject(payload);
}
} else {
reject(payload);
}
})
.catch((e) => {
if (axios.isCancel(e)) {
console.log("Request canceled", e.message);
} else {
// handle error
}
reject(e);
});
});
};
export default instance;
And finally i set my token on authentification with a dispatch
const dispatch = useDispatch()
<ReactKeycloakProvider onTokens={({token}) => dispatch(authUser(token))} authClient={Keycloak(config)}
initOptions={{
onLoad: 'login-required',
checkLoginIframe: false,
timeSkew: "0",
refreshToken: ""
}}
LoadingComponent={<div />}
>
....
</ReactKeycloakProvider>
Most probably the application content is being rendered before the onTokens is being executed. Try checking on the existence of the token in the store state before rendering anything (or show a loading screen).

How to call ToastsStore.success or ToastsStore.error after API response in React component?

I created a component that contains the "New Article" form. The user can add a new article after clicking the Save button. The click event calls this.props.fetchAddPaper(data), which saves the article to the database.
If the response is 200, I would like to display information on the page for the user that the article has been successfully saved.
If the response is 500 or 400 or 401, I would like to display information that 'something went wrong try again'. To display alerts I use react-toasts. My question is: how can I get a response from the API after clicking the Save button so that you can display a success or error alert? How do I get a response from this.props.fetchAddPaper (data) in the handleSubmit method that I am calling?
Below is the fetchAddPaper that connects to the API. How do I get a response from such a method in a component?
const apiMiddleware = ({ dispatch }) => next => action => {
next(action);
if (action.type !== 'API')
return;
let {
url, // Endpoint address, relative to $HOST/api/
method, // http method (GET, POST, DELETE etc.)
params, // URI string params
data, // Post data
onSuccess, // Function accepting response. If redux action is returned, it will be dispatched
onFinish, // Function run on either success or error
onError, // Function accepting error
onValidationError, // Function accepting response with validation error
text, // Loading text. If not provided there will be no overlay while loading data
successText // Success text, shown on green bar. If not provided it won't be shown
} = action.payload;
// Allow for onSuccess, onFinish and onError to be either redux (and thunk) actions or normal functions
const conditionalDispatch = (action) =>
action && _.isFunction(action) ? dispatch(action) : action;
const request = {
headers: {
'Accept': 'application/json'
},
url: `${host}/api/${url}`,
method,
timeout: 180000
};
if (params) {
params = { ...params };
for (let prop in params) {
if (Array.isArray(params[prop])) {
const arrayData = arrayToGetParameters(params[prop], prop);
delete params[prop];
Object.assign(params, arrayData);
}
}
}
if (data) {
if (method.toUpperCase() === "GET" || method.toUpperCase() === "DELETE") {
throw new Error("Can't add request data to get or delete method");
}
request.headers['Content-Type'] = 'application/json;text/plain;text/json';
}
request.data = data;
request.params = params;
text && dispatch(onLoadingStart(text));
let notificationId = shortId.generate();
axios.request(request)
.then((response) => {
text && dispatch(onLoadingEnd());
onSuccess && dispatch(onSuccess(response.data));
onFinish && conditionalDispatch(onFinish);
if (successText) {
dispatch(onAddFlashMessage({type: 'success', text: successText, id: notificationId}));
setTimeout(() => {
dispatch(onDeleteFlashMessage(notificationId));
}, 5000);
}
})
.catch((error) => {
onFinish && conditionalDispatch(onFinish);
// onError && conditionalDispatch(onError(error));
onError && dispatch(onError(error));
dispatch(onLoadingEnd());
if (error.response && error.response.status === 401) {
//dispatch(onLogOut()); todo: wylogowanie
return;
}
if (error.response && error.response.status === 422 && onValidationError) {
conditionalDispatch(onValidationError(error));
}
else {
dispatch(onAddFlashMessage({...httpReqErrorHandler(error), id: notificationId}));
}
setTimeout(() => {
dispatch(onDeleteFlashMessage(notificationId));
}, 5000);
});
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ON_FETCH_ADD_PAPER:
return {
...state,
paper: action.response
};
default:
return state;
}
const onFetchAddPaper = (response) => ({ type: actionTypes.ON_FETCH_ADD_PAPER, response });
export const fetchAddPaper = (data) => {
return (dispatch) => {
dispatch({
type: 'API',
payload: {
url: 'Papers/addPaper',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
},
data: data,
onSuccess: (response) => onFetchAddPaper(response),
onError: (error) => onFetchAddPaper(error)
}
});
};
};
handleSubmit(e) {
e.preventDefault();
let data = {
title: this.state.title,
header: this.state.header
}
this.props.fetchAddPaper(data);
console.log(this.props.paper);
//when the user first clicks the save button, the response is empty, but the second time the response has a value 200
}
function mapStateToProps(state) {
return {
paper: state.paper.paper
}
};
function mapDispatchToProps(dispatch) {
return {
fetchAddPaper: data => dispatch(fetchAddPaper(data))
}
}
//initialstore.jsx
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import apiMiddleware from './ApiMiddleware';
import rootReducers from '../RootReducers';
export default function initStore() {
const store = createStore(
rootReducers,
compose(
applyMiddleware(thunk, consoleMessages, apiMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f
)
);
if (module.hot) {
module.hot.accept('../RootReducers', () => {
const nextRootReducer = require('../RootReducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
You can return a promise from your fetchAddPaper action
Something like this:
export const fetchAddPaper = (data) => {
return (dispatch) => {
return new Promise((resolve,reject) => {
dispatch({
type: 'API',
payload: {
url: 'Papers/addPaper',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
},
data: data,
onSuccess: (response) => {
onFetchAddPaper(response);
resolve(response); //return your promise on success
},
onError: (error) => {
onFetchAddPaper(error);
reject(error); //return your promise on failure
}
}
});
})
};
};
So, whenever your action executes, it'll be a promise which you can then evaluate like -
this.props.fetchAddPaper(data).then(response => {..do something})

Branch coverage zero percent in jest

I have written some test cases and everything seems fine except the following one. I am getting zero branch cover for one file. I have googled couple of blog and I came to understand if the statement cab be executed in multiple scenario that call branch coverage. But I don't find my code can be executed in multiple way.
request.js
import axios from 'axios';
export default async (request, httpService = axios) => {
const {
method, url, data, headers,
} = request;
return httpService.request({
method,
url,
headers: Object.assign({}, headers),
data,
});
};
reqeust.test.js
describe('requestServie', () => {
it('should have a valid request object', async () => {
const requestObj = {
method: 'POST',
url: 'http://mock.url',
data: {},
};
const mockRequest = jest.fn(() => Promise.resolve({}));
const httpService = {
request: mockRequest,
};
await request(requestObj, httpService);
expect(mockRequest).toHaveBeenCalledWith({
method: requestObj.method,
url: requestObj.url,
headers: {},
data: requestObj.data,
});
});
it('should return a valid response (empty)', async () => {
const response = {
data: {
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {},
};
const mockRequest = jest.fn(() => Promise.resolve(response));
const httpService = {
request: mockRequest,
};
const res = await request({ url: 'http://mock.url' }, httpService);
expect(res).not.toBe(null);
expect(res).toMatchObject(
{
status: response.status,
},
);
});
});
Edit
rquest.js
export default async (request, httpService = axios) => {
const {
method, url, data, headers,
} = request;
return httpService.request({
method,
url,
headers: Object.assign({}, headers),
data,
}).then(successResponse, (error) => {
throwHttpError(error);
});
};
request.test.js
import HttpError from 'standard-http-error';
import axios from 'axios';
import request, { successResponse, throwHttpError } from './requestService';
describe('requestService', () => {
jest.mock('axios', () => ({
request: jest.fn(() => Promise.resolve({})),
}));
describe('successResponse', () => {
const mockRes = {
status: 9001,
data: {
stuff: 'stuff',
},
};
it('should returns an object with only status and data properties', () => {
const responseKeys = Object.keys(successResponse(mockRes));
expect(responseKeys).toMatchObject(['status', 'data']);
expect(responseKeys.length).toBe(2);
});
it('should map the status of the reponse to the status property', () => {
expect(successResponse(mockRes).status)
.toBe(mockRes.status);
});
it('should map the data of the reponse to the data property', () => {
expect(successResponse(mockRes).data)
.toMatchObject(mockRes.data);
});
it('should have a valid request object', async () => {
const requestObj = {
method: 'POST',
url: 'http://mock.url',
data: {},
headers: {},
};
const mockRequest = jest.fn(() => Promise.resolve({}));
const httpService = {
request: mockRequest,
};
await request(requestObj, httpService);
expect(mockRequest).toHaveBeenCalledWith({
method: requestObj.method,
url: requestObj.url,
headers: {},
data: requestObj.data,
});
});
});
describe('httpThrowError', () => {
const mockErr = {
response: {
status: 9001,
statusText: 'error message goes here',
},
};
it('should map the status of the reponse to the error.status property', () => {
try {
throwHttpError(mockErr);
} catch (e) {
expect(e).not.toBe(null);
expect(e.status).toBe(mockErr.response.status);
expect(e.message).toBe(mockErr.response.statusText);
}
});
it('should map the data of the reponse to the error.data property', () => {
const mockErrWithData = Object.assign({}, mockErr);
mockErrWithData.response.data = {};
try {
throwHttpError(mockErrWithData);
} catch (e) {
expect(e).not.toBe(null);
expect(e.data).toBe(mockErrWithData.response.data);
}
});
});
describe('request', () => {
const testCases = [
['should return error response on server error', 500],
['should return error response on bad request', 400],
['should return error response on unauthorised', 401],
];
testCases.forEach(([testName, errorStatus]) => {
it(testName, async () => {
const errorResponse = {
response: {
status: errorStatus,
},
};
const mockRequest = jest.fn(() => Promise.reject(errorResponse));
const httpService = {
request: mockRequest,
};
try {
await request({ url: 'http://mock.url' }, httpService);
throw new Error('Expected an exception, but none was thrown');
} catch (err) {
expect(err).not.toBe(null);
expect(err).toMatchObject(
new HttpError(errorResponse.response.status,
errorResponse.response.statusText),
);
}
});
});
it('should return an valid response (empty)', async () => {
const response = {
data: {
meta: {},
results: [],
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {},
};
const mockRequest = jest.fn(() => Promise.resolve(response));
const httpService = {
request: mockRequest,
};
const res = await request({ url: 'http://mock.url' }, httpService);
expect(res).not.toBe(null);
expect(res).toMatchObject(
{
status: response.status,
data: response.data,
},
);
});
it('should use axios by default', async () => {
const req = { url: 'http://mock.url', method: 'get' };
await request(req);
expect(axios.request).toHaveBeenCalled();
});
});
});
Error
Updated 15/Nov/18
"jest": "^23.6.0",
import HttpError from 'standard-http-error';
import axios from 'axios';
import request, { successResponse, throwHttpError } from './requestService';
jest.mock('axios', () => ({
request: jest.fn(),
}));
Error
To see what is not covered you can go to coverage/Iconv-report and open index.html. Those are created once you run jest with --coverage option.
It looks like uncovered branch is: httpService = axios. So you need to check if default axios is used.
To cover that you may run request without httpService argument - you can mock axios globally for that, i.e.:
import axios from 'axios';
// note that mock goes before any describe block
jest.mock('axios', () => {
return {
request: jest.fn(() => Promise.resolve({})),
}
});
describe('requestService', () => {
// ... your other tests
it('should use axios by default', async () => {
const opts = { url: 'http://mock.url', method: 'get' };
const res = await request(opts);
expect(axios.request).toHaveBeenCalled();
});
});
Note that jest.mock have some buggy behavior when running inside a spec.

Resources