Automatic handle 401 response with redux-saga and fetch - reactjs

I'm building a "secured" application and using redux-saga together with fetchjs for doing the async calls to the backend.
My backend returns a 401 status code when the user is not authorized, i want to catch this "exception" globally and dispatch a action so my react application goes to the login screen.
I found the following solution: https://github.com/redux-saga/redux-saga/issues/110, but in this case the handling for the 401 should be explicit in every saga that we build.
By adding code to every saga it becomes more complex. It also increases the chances a developer forgets to add the code for handling the 401 code.
Is there a nice way to handle this 401 response globally?

I would not use redux-saga since it does not have ability to do what you require.
Instead, when setting up store, API layer and other things configure API layer do invoke handler on every error occurred.
Sample of API layer that reports invokes error handler.
const conf = {
onError: () => {},
}
api.onError = (cb) => {
conf.onError = cb;
}
api.get = (url) => {
fetch(url)
.then(response => {
if (response.ok === false) {
return conf.onError(response);
}
return response;
})
// Network error
.catch(conf.onError)
}
Configure application.
import store from './store';
// Configure your API wrapper and set error callback which will be called on every API error.
api.onError((error) => {
store.dispatch({
type: 'API_ERROR',
payload: error,
});
});
// In you reducers...
isAuthorized(state, action) {
if (action.type === 'API_ERROR' && action.payload.statusCode === 401) {
return false;
}
return state;
}
Then, call API as usually, If error occurs, action is dispatched to store and you may or may not react to this actions.

Related

Axios promise resolving on pre-flight request response which makes associated GET execute out of order with rest of app

Good day,
I am working on a React app that makes use of React-Redux (with Thunk) and Axios.
I have an action that I dispatch which makes an authenticated API call. Due to the fact that I have Authorization headers on a cross-origin request, there is a pre-flight request.
The problem that I have is that Axios seems to be running the .then() code once the pre-flight request returns rather than when the associated GET request returns. This results in the Reducer function updating state before the results of the API GET request return.
I have added some console.logs to give more details to illustrate the problem. As you can see the pre-flight request is sent in the first entry. The .then executes one the pre-flight request returns with 200. The action then fires off and the reducer updates the state. My app responds by re-rendering the container that was connected to Redux. The child components also then update. Then the GET request completes and returns with a 200. And at this point nothing further happens because the reducer was already updated in the prior .then() mentioned above.
The action code is shown below. I have not pasted all the other code in as there are a number of files and they are relatively big. If needed I can include those too.
export const updatePlotDataInit = (datasetName, tableName, xFieldName,
yFieldName, chartId, chartType, plotId, newDomainStartDbIndex, newDomainEndDbIndex) => {
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [start of function]');
return dispatch => {
dispatch(updatePlotDataStart());
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [just before api request]');
instance.get( // custom axios instance with extra auth header used here
`/v1/datasets/${datasetName}/tables/${tableName}/fields/data?xField=${xFieldName}&yField=${yFieldName}&chartType=${chartType}&domainStart=${newDomainStartDbIndex}&domainEnd=${newDomainEndDbIndex}`
)
.then(response => {
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [in .then before updatePlotDataSuccess]');
// dispatch redux action for success case
const currentLevel = response.data.metaData.current_level
const data = response.data.queryData.data //schema is available too
//datasetId, tableId, xFieldId, xField, yFieldId, yField, chartId, plotIdVal, currentLevel, data
dispatch( updatePlotDataSuccess( plotId, currentLevel, data ) );
// console.log(response);
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [in .then after updatePlotDataSuccess]')
})
.catch(error => {
console.log(error);
// dispatch redux action for failure case
dispatch(updatePlotDataFail(error));
})
}
};
I am not entirely sure but it seems that Axios is seeing the successful pre-flight response as suitable to resolve the promsie and hence the .then gets executed.
Does this seem to be the case? And if so how would I force Axios to wait for the GET/POST/PUT/etc to succeed before resolving the promise?
Any help is appreciated.
I know it is for long time ago, but I think it could be useful for others who find this issue similar to their problem, with no answer...
for me it was just because of a careless coding :D,
here is my response inceptor, I missed "return" before Promise.resolve(axios(originalRequest));
I solved it by adding return :
AxiosInstance.interceptors.response.use(
(response) => {
return response;
},
function (error) {
const originalRequest = error.config;
let refreshToken = localStorage.getItem("refreshToken");
if (
refreshToken &&
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
return axios
.post(apiUrl + `auth/refreshtoken`, { refreshToken: refreshToken })
.then((res) => {
if (res.status === 200) {
localStorage.setItem("accessToken", res.data.accessToken);
console.log("Access token refreshed!" + res.data.accessToken);
originalRequest.headers.Authorization = 'Bearer ' + res.data.accessToken;
//*************** I just return promise.resolve *****************//
return Promise.resolve(axios(originalRequest));
}
}).catch((error) => {
console.log(error);
});
}
return Promise.reject(error);
}

How do I replace route in react redux after thunk error response?

I am using react with redux. In the redux layer I am using thunk. So component calls action, redux action calls a new layer -> service. The service handles http calls. When response is ok - I use the dispatch that thunk provides and return to the reducer. when the response is error I want to redirect the user and I couldn't find a way to do so. I am missing the router and its replace method.
You could catch inside the component which triggered the action creator
function doAction() {
return (dispatch) => {
return fetch(...).then(...)
}
}
class Component extends React.Component {
onClickButton = () => {
this.props.doAction().catch(() => {
this.props.router.replace(...)
})
}
}
I'm assuming you've connected properly and router is being passed in as a prop (or a context - doesn't really matter so long as it's available in the component)
one downside to this is that you have to replicate this functionality whenever you use that action creator. You could provide the router as an argument to the action creator, but I'm not sure if that's against standard practice/an anti-pattern.
Another option is to hard-redirect using window.location
Ok, I think I've got it. I created a general redux action called ResponseError. I will dispatch this action in all my services, every time an HTTP call returns with error. Then in the reducer I'll just turn on a boolean flag on the store and in the main application page I'll listen to this flag. If it is on -> do whatever you want.
If you are using axios for the service that handles http calls, you can use its interceptor. This way, you do not need to have a handler on every action/reducer-action.
import axios from 'axios';
axios.interceptors.response.use(
res => interceptResponse(res),
err => interceptError(err)
);
const interceptResponse = response => {
// nothing to do here, return back response
return response;
};
const interceptError = error => {
if (error.response.status === 404 || error.response.status === 403) {
// redirect to another react router page
history.push('/error404');
} else if (error.response.status === 401) {
return Promise.reject(error);
}
};
Referenced from my sample project on GitHub: https://github.com/mosufy/serverless-react/blob/develop/client/site/src/lib/sdk.js#L90

dispatch redux action only if a previous action has success

the situation is as follows: I need to call some API endpoints passing an auth token I previously requested. The problem is this token can expire, so each time I want to call an endpoint to retrieve data I need to ensure the token is valid and renew if necessary.
Currently I have an async action that renews the token and another set of actions that retrieves data from API.
My goal is to make some kind of aspect-programming/composition of both actions, like:
renewTokenIfNeeded = ... // action that check if token has expired and renew it
getData = ... // Retrieves data from API
realGetData = compose(renewTokenIfNeeded, getData);
The idea is realGetData action checks if token is expired, calling the renewTokenIfNeeded and only if this action success then call the getData.
Is there any pattern for this? Thanks.
I think you can create an action that returns a function instead of an object and use redux-thunk middleware. Actions that return a function are not pure and you can make more function calls and perform multiple dispatches within the action. So, it could look something like:
getData = () => async (dispatch, getState) => {
await renewTokenIfNeeded()
getData()
// Any dispatches you may want to do
}
Make sure you add the redux-thunk middleware to your store as mentioned in the documentation link.
You can use redux thunk or any other middleware (e.g redux saga) to solve the token issue. Redux thunk lets action creators return a function instead of an action.
export function checkTokenValidity(token) {
return (dispatch) => {
dispatch(TOKEN_CHECK_REQUEST());
return (AsyncAPICall)
.then((response) => {
dispatch(TOKEN_CHECK_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function getData(id) {
return (dispatch) => {
dispatch(GETDATA_REQUEST());
return (AsyncAPICall(id))
.then((response) => {
dispatch(GETDATA_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function checkTokenAndGetData(token,id) {
return (dispatch) => {
dispatch(checkTokenValidity(token)).then(() => {
dispatch(getData(id));
});
};
}
The problem with this approach is that code gets messy easily as you have to combine the checking of Token and an arbitrary API call every time.
This is a little better.
(2) (If you like to call it in your action creator for state tracking) Pass the token in the request, have a middleware to intercept the request and change the response to "Invalid Token/Expired Token", or the real data to have a clean and readable frontend code.
export function getData(token,id) {
return (dispatch) => {
dispatch(GETDATA_REQUEST());
return (AsyncAPICall(id))
.then((response) => {
//if response is EXPIRED token
dispatch(renewToken()); // renew token is another action creator
//else
dispatch(GETDATA_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
My suggestion, is to move your token validation in your backend.
(3)Always pass the token in the request, have a middleware to intercept the request, if it is expired, renew it in the backend code. Add an identifier to your response that it was expired and renewed it. Much simpler.

How do I make an HTTP request in react-redux?

I am just getting started with react and I'm a bit lost. I'm trying to make a login page and make a http post request. Right now I'm just trying to get any type of HTTP request working, so I'm using request bin and I found this basic action in the docs for an npm package (https://www.npmjs.com/package/redux-react-fetch):
export function updateTicket(ticketId, type, value){
return {
type: 'updateArticle',
url: `http://requestb.in/1l9aqbo1`,
body: {
article_id: ticketId,
title: 'New Title'
},
then: 'updateTicketFinished'
}
}
So, after writing an action, what do I do? How do I actually get my app to call on and use that action? The docs for the npm package mention something about setting a state in my store, is that something I need to set up first?
You can try any of the following. I have used both fetch and axios they work amazingly well. Yet to try superagent.
For making requests you can either use fetch with
fetch-polyfill for compatibility across all browsers (link)
Axios library. (link)
Superagent with promises.(link)
If you use fetch you would need to use polyfill since it is not supported in IE and safari yet. But with polyfill it works pretty well. You can check out the links for how you can use them.
So what you would doing is in your action creator you can call an API using any of the above.
FETCH
function fetchData(){
const url = '//you url'
fetch(url)
.then((response) => {//next actions})
.catch((error) => {//throw error})
}
AXIOS
axios.get('//url')
.then(function (response) {
//dispatch action
})
.catch(function (error) {
// throw error
});
So that was for the API call, now coming to the state. In redux there is one state which handles your app. I would suggest you should go through redux basics which you can find here . So once your api call succeeds you need to update your state with the data.
Action to fetch data
function fetchData(){
return(dispatch,getState) =>{ //using redux-thunk here... do check it out
const url = '//you url'
fetch(url)
.then (response ) => {dispatch(receiveData(response.data)} //data being your api response object/array
.catch( error) => {//throw error}
}
}
Action to update state
function receiveData(data) {
return{
type: 'RECEIVE_DATA',
data
}
}
Reducer
function app(state = {},action) {
switch(action.types){
case 'RECEIVE_DATA':
Object.assign({},...state,{
action.data
}
})
default:
return state
}
}

Better ways to handle REST API call in Redux

In my app, I need to call several REST API endpoints:
// The UI Class
class LoginForm extends Component {
handleSubmit(){
store.dispatch(login(username, password));
}
}
// An action
function login(username, password){
return dispatch => {
fetch(LOGIN_API, {...})
.then(response => {
if (response.status >= 200 && response.status < 300){
// success
} else {
// fail
}
})
}
}
The gist is above and easy to understand. User triggers an action, an ajax call to the corresponding endpoint is made.
As I am adding more and more API endpoints, I end up with a bunch of functions similar to the skeleton of the login function above.
How should I structure my code in such a way that I don't repeat myself with duplicate ajax functions?
Thanks!
I strongly suggest you to read this popular github sample project. At first it is hard to understand but don't worry and continue to read and realize what is happening in that.
It uses very clear and simple way to handle all of your API calls. when you want to call an API, you should dispatch an action with specific structure like this:
{
types: [LOADING, SUCCESS, FAIL],
promise: (client) => client.post('/login', {
data: {
name: name
}
})
}
and it will handle these kind of actiona by a custom middleware.
The way I handle a similar situation is to have 2 wrapper for API calls:
function get(url) {
return fetch(url)
.then(response => {
if(response.status >= 200 && response.status < 300) {
return response
}
else {
let error = new Error(response.statusText)
error.response = response
throw error
}
})
.then(response=> response.json())
}
This wrapper will take a url and return the json data. Any error that happens (network, response error or parsing error) will be caught by the .catch of get
A call basically looks like that:
get(url)
.then(data => dispatch(someAction(data)))
.catch(error => dispatch(someErrorHandler(error)))
I also have a post wrapper that in addition sets the header for CSRF and cleans the data. I do not post it here as it is quite application-related but it should be quite ovious how to do it.

Resources