Better ways to handle REST API call in Redux - reactjs

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.

Related

Using interceptors with react-query

I have a bunch of custom react-query hooks in my app, e.g.:
export const useItems = () => {
return useQuery(["items"], async () => {
const response = await axios.get("/api/items");
if (response.status !== 200) {
throw response.data.error ?? "Unknown error";
}
return response.data;
});
}
export const useVenueSearch = (placeId) => {
return useQuery(["places", placeId], async () => {
const response = await axios.get(`/api/places/${placeId}`);
if (response.status !== 200 && response.status !== 404) {
throw response.data.error ?? "Unknown error";
}
return response.data;
});
}
...
At any point in time, in any of these custom hooks API calls, the server may decide that the user needs to accept the new Terms and Conditions in order to continue using the product. The server returns a known 4xx response in this case.
What's the best way to centrally handle this 4xx response and update React's state (to show a modal asking the user to accept the Terms and Conditions)?
Bonus question (similar scenario)
Given a bunch of custom mutation hooks, I'd like to introduce an artificial delay so requests never finish before 1000ms passed. What would be a good central place to put this logic?
you can use the interceptor provided by axios and u can even use the retry method of it to hit the API again.
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
you can read more about it here. https://axios-http.com/docs/interceptors
but apart from this you need to create your own wrapper which will do this part but react-query didn't provide interceptor.

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);
}

React Redux wait for state change

I Want to extract all my server call functions (HTTP GET, POST, ...) into one helper function. The helper function will be called from different react components. It will check if the JWT Token is expired or not (client side) and if expired will ask user to relogin and get a new token before sending the request to the server.
Problem: When the helper function finds out the Token is expired it will dispatch an action to show the Login Dialog however it will continue the code calling the fetch. I need to wait for the Login Dialog to change the LoggedIn state and token before calling the server however it doesn't seem to be possible to watch for this state change. (One idea is returning a promise from Login dialog however I can't understand how to return a promise and where from!)
I appreciate that all the above can be very abstract and difficult to follow so have created a full code example showing what I need.
Sample Code
*PS : If the code sandbox fails please refresh it. They seem to be having some race issue with some of the plugins!
Is this what you are looking for?
componentDidUpdate(prevProps) {
if (!prevProps.loggedIn && this.props.loggedIn) {
// User just logged in
}
}
I am not a specialist of thunk yet, but what i can say is that you serverCall function must return a function with a dispatch parameter (see examples here)
You must dispatch an action in this sub function (in fact, call an action creator which will put the data in the application state.
You don't have to make an explicit promise because fetch return already a promise.
I will try something like :
export const serverCall = (
url,
method,
body = undefined,
successMessage = undefined
) => {
// your code
return function (dispatch) {
return fetch(url, {
method: method,
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
...(body && { body: JSON.stringify(body) })
}).then(response => response.JSON())
.then(response =>
if (response.ok) {
if (successMessage) {
console.log(successMessage);
}
dispatch(fetchData(response))
} else {
index.js
<Button
onClick={() =>
this.props.serverCall(
"https://jsonplaceholder.typicode.com/users",
"GET"
)
>
The state is to be removed here if you use Redux. All is taken from props via mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
onLogin: (username, password) => dispatch(login(username, password)),
ToggleIsLoginDialogOpen: IsLoginDialogOpen =>
dispatch(toggleIsLoginDialogOpen(IsLoginDialogOpen)),
serverCall: (url, method) => dispatch(serverCall(url, method))
});

Automatic handle 401 response with redux-saga and fetch

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.

Data fetching with React Native + Redux not working

I am building my first React Native app and use Redux for the data flow inside my app.
I want to load some data from my Parse backend and display it on a ListView. My only issues at the moment is that for some reason, the request that I create using fetch() for some reason isn't actually fired. I went through the documentation and examples in the Redux docs and also read this really nice blog post. They essentially do what I am trying to achieve, but I don't know where my implementation differs from their code samples.
Here is what I have setup at the moment (shortened to show only relevant parts):
OverviewRootComponent.js
class OverviewRootComponent extends Component {
componentDidMount() {
const { dispatch } = this.props
dispatch( fetchOrganizations() )
}
}
Actions.js
export const fetchOrganizations = () => {
console.log('Actions - fetchOrganizations');
return (dispatch) => {
console.log('Actions - return promise');
return
fetch('https://api.parse.com/1/classes/Organization', {
method: 'GET',
headers: {
'X-Parse-Application-Id': 'xxx',
'X-Parse-REST-API-Key': 'xxx',
}
})
.then( (response) => {
console.log('fetchOrganizations - did receive response: ', response)
response.text()
})
.then( (responseText) => {
console.log('fetchOrganizations - received response, now dispatch: ', responseText);
dispatch( receiveOrganizations(responseText) )
})
.catch( (error) => {
console.warn(error)
})
}
}
When I am calling dispatch( fetchOrganizations() ) like this, I do see the logs until Actions - return promise, but it doesn't seem to actually to fire off the request. I'm not really sure how how I can further debug this or what resources to consult that help me solve this issue.
I'm assuming that Redux is expecting a Promise rather than a function.. Is that true?
If so, I think your return function may not be working.
You have a new line after your return, and it's possible JavaScript is (helpfully) inserting a semicolon there.
See here: Why doesn't a Javascript return statement work when the return value is on a new line?

Resources