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.
Related
I want to show/call a custom react error component. Below is my code snippet. I am getting error since my axios interceptor is neither a react component nor a custom Hook.
axios.interceptors.response.use((response) => {
if (response.config.parse) {
//perform the manipulation here and change the response object
}
return response;
}, (error) => {
//Want to call the component here
return Promise.reject(error.message);
});
I don't think you can render a component in an axios interceptor.
The interceptor will intercept every response you make, and if there is an error do something with the error.
Meaning React would have no way of knowing what to render in which part of the DOM based on an error on any response from axios. Here is a good explanation of how interceptors work and good use cases for them
If you want to render an error message to the user then don't intercept the response. Just wait until the response errors (in a normal request not an interceptor) then pass that error to your own error component.
In practice that might look something like the below
function apiQuery (url) {
const [error, setError] = useState(null);
const [api, setApi] = useState(null);
axios.get(url)
.then(function (response) {
// handle success
setApi(response.data)
})
.catch(function (error) {
// handle error
setError(error)
})
return { api, error }
}
Now you have access to the error response which you can pass into your own error component
const api = apiQuery('/users')
// pass error into your bespoke component
if (api.error) return <Error error={error}/>
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);
}
I have developing mern stack web site. In that I have added below codes to handle logging.
onSubmit(e) {
e.preventDefault();
const obj = {
user_name: this.state.user_name,
password: this.state.password
};
axios.post('http://localhost:4000/login', obj)
.then(res=> localStorage.setItem('token',(res.data.token))
//localstorage.setItem('username','res.data.user.username)
)
}
When I click on login button this onSubmit() function called and will save token in local storage.
But, res.data have more details. (from backend it passes logged users information too)
So I want to add those to local storage. I tried that as commented in above function. It says error in res. Note : I user react for frontend.
Also I want to handle handle errors in any cases axios.post() didn't work as planned. In server side it send different messages for unmatched credentials and wrong passwords. How can I show those in my page. Thank you.
Since the only accepted data type in localStorage is string, you should stringify it first using JSON API.
const userDataStr = JSON.stringify(res.data);
localStorage.setItem('userData', userDataStr);
Now if you want to access the userData from localStorage you just need to convert it back to javascript object.
const userDataStr = localStorage.getItem('userData', userData);
const userData = JSON.parse(userDataStr);
You can have multiple catch in the returned promise of axios.post
axios.post()
.catch((error) => { })
.catch((error) => { })
But those catch will called with the same error so you need to handle it differently in each catch
Another suggestion:
If you want to easily handle the error, you can use higher order function like this
const handleError = (status, callback) => (error) => {
if (status === error) {
callback(error);
}
}
axios.post()
.catch(handleError(404, (error) => { /* only called when status === 404 */ }))
.catch(handleError(500, (error) => { /* only called when status === 500 */ }))
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.
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.