Very slow response from action creator React/Redux - reactjs

I am getting super slow response times (upwards 10 seconds) for a function to be called in my action creator.
export function acceptContract(id) {
return function(dispatch) {
const config = { headers: { authorization: localStorage.getItem('etherToken') } };
const data = { data: id };
axios.put('/pending-contracts/accept',
data,
config
).then( response => {
console.log(response);
getPendingContracts();
})
.catch( response => {
// If the get doesn't work, boot the user out to index.
console.log(response);
});
}
}
I update one of the values of contracts in my DB, and I want redux to then dispatch the new list for the user to show the update on the UI.
Not sure why the getPendingContract() invocation takes so long. I get the response from my backend almost immediately.
export function getPendingContracts() {
return function(dispatch) {
axios.get('/pending-contracts', {
headers: { authorization: localStorage.getItem('etherToken') }
})
.then( response => {
console.log('in getPendingContracts')
return dispatch({
type: PENDING_CONTRACTS_LIST,
payload: response.data.message
});
})
.catch( response => {
// If the get doesn't work, boot the user out to index.
console.log(response);
});
}
}

The issue might be related to how you are calling getPendingContracts from acceptContract. You are just calling the function directly, without dispatching it. As far as i can tell all that would do is return you a function that never gets invoked, not sure how you get a response at all. Change the call to this:
then( response => {
console.log(response);
dispatch(getPendingContracts());
})

Related

In React, is there a way to apply a filter to responses obtained through fetch?

Using React 16.12.0. We have a number of fetch calls that resemble
const handleFormSubmit = (e) => {
e.preventDefault()
if (password != passConfirm) {
//handle password doesn't match password confirm on submit
setErrors({passConfirm: ["Must match password"]})
return
}
fetch(REACT_APP_PROXY + '/users/', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'Authorization': `Token ${sessionStorage.getItem('token')}`},
body: JSON.stringify({first_name, last_name, username, password, email})
}).then((response) => {
if (response.ok) {
sessionStorage.setItem('token', response['Refresh-Token'])
setRedirect(true)
} else {
setErrors(response)
console.log(response)
}
})
Note the section where we have an ".ok" response
sessionStorage.setItem('token', response['Refresh-Token'])
We will have a number of these endpoints where we will want to extract this response header and place it in local storage. Is there a more elegant way of applying a response filter to certain endpoints to implement this behavior as opposed to the way we are doing it now?
I don't think there is a possibility for this using fetch except monkey patching fetch itself:
const { fetch: originalFetch } = window;
window.fetch = async (...args) => {
let [url, options] = args;
// you can run any request logic here
const response = await originalFetch(url, options);
// you can run any response logic here
return response;
};
But, there must be someone that already did this, so I stumbled upon this npm library fetch-intercept that you can use to do the similar:
import fetchIntercept from 'fetch-intercept';
const unregister = fetchIntercept.register({
response: function (response) {
// Do something with the response
if (response.ok) {
sessionStorage.setItem('token', response['Refresh-Token'])
}
return response;
},
});
// you can call unregister if you don't want run the interceptor anymore
unregister();
Please be aware of the following: You need to require fetch-intercept before you use fetch the first time.
If you are willing to switch from fetch to axios, you can use interceptors and handle it globally.
import axios from 'axios'
axios.interceptors.response.use(response => {
if (response.headers['Refresh-Token']) {
sessionStorage.setItem('token', response.headers['Refresh-Token'])
}
return response
})
Reference: https://axios-http.com/docs/interceptors
Use a function.
async function makeHttpCall(successCallbk) {
var response = await fetch(...arguments);
if(response.ok) {
sessionStorage.setItem('token', response['Refresh-Token']);
successCallback.call();
} else {
setErrors(response);
console.log(response);
}
}

Waiting for call to finish and then dispatch action in saga

I want to make call to server and then use that data for dispatch of other action.
export function* function1(actions) {
console.log('inside');
try {
console.log('getting past orders list');
const url = `/api/getOrders`;
let reqsData = {
order_id: actions.payload.order_id
};
const data = yield call(request, { url, method: 'POST', data:reqsData })
console.log(data);
console.log('///////////////////////////////////');
if (!data.error) {
console.log(data)
yield put({ type: 'nowThis', payload: actions.payload.data });
} else {
console.log('---------------------------------')
console.log('got some error');
}
} catch (error) {
console.log(error)
}
}
But It is not running code next to line
const data = yield call(request, { url, method: 'POST', data:reqsData })
I have similar code before which is running properly + i checked the network and i am getting response 200 for this line.
I have used fork in place of call but it run my code next to that line before the call is complete.
yield call takes function and arguments. Write a method to make a service call. U can use axios npm package (axios.get('../url',params:{params})) and call that function in yield call.
yield call(methodToCallApi(),params,to,method). also, it is better if you keep services calls in a seperate file and just call those methods in saga, instead of defining directly in saga.
It seems your request method is not returning properly. Wrap that in a Promise:
request() {
return new Promise(resolve => {
myApiCall().then(response => {
resolve(response);
}).catch(e => {
reject(e);
});
});
}
and then in your saga, you can yield as normal:
const data = yield call(request, { url, method: 'POST', data:reqsData })

Axios-Redux in React to an Express endpoint-both .then and .catch triggered

I'm using a Redux Form to send a POST call to an Express endpoint. The endpoint is supposed to return the successfully saved object, or an error.
The endpoint successfully saves the posted data and returns the JSON. But Axios in the Redux action picks up both the .then and the .catch triggers-in the following action, it logs the following:
successful response: { …}
failure response: undefined
What am I doing wrong?
My Axios action:
export function addPlot(props) {
const user = JSON.parse(localStorage.getItem('user'));
return function(dispatch) {
axios
.post(
`${ROOT_URL}/plots`,
{
props
},
{ headers: { authorization: user.token } }
)
.then(response => {
console.log('successful response: ', response.data);
const plotModal = document.getElementById('plotModal');
plotModal.modal('dispose');
dispatch({ type: PLOT_ADDED, payload: response.data });
dispatch({ type: ADDING_PLOT, payload: false });
dispatch({
type: NEW_PLOT_GEOJSON,
payload: ''
});
})
.catch(response => {
console.log('failure response: ', response.data);
dispatch(authError(PLOT_ADD_FAILURE, 'Failed to add plot'));
});
}
My endpoint:
exports.newPlot = async (req, res, next) => {
console.log(JSON.stringify(req.body.props));
let company;
if (req.user.companyCode !== 'Trellis') {
company = req.user.companyCode;
} else {
company = req.body.props.company;
}
const {
name,
feature,
growerPhone,
plotCode,
rootStock,
region,
variety,
grower,
planted
} = req.body.props;
const plot = new Plot({
name,
grower,
variety,
planted,
region,
rootStock,
plotCode,
growerPhone,
feature,
company
});
try {
const newPlot = await plot.save();
res.json(newPlot);
} catch (e) {
console.log("couldn't save new plot", JSON.stringify(e));
return res.status(422).send({ error: { message: e, resend: true } });
}
};
You could use redux-thunk middleware to manage async actions.
The problem I see is that you are not dispatching the axios action, you must call dispatch(this.props.addPlot(props))in order to do something in the redux store.

Dispatch async redux action from non-react component with thunk middleware

I am building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}

Is there a way to set global axios config for error response codes

I'm using axios in my react/redux application and when I get errors like 401, 404, etc I currently have to deal with them for each action function when I make the calls to axios. I have a axios_config.js where I've wrapped the axios calls with some common idioms. For example:
// need to move this to app config
const BASE_URL = 'http://localhost:8080/api/';
function config() {
return {
headers: {'X-Token-Auth': localStorage.getItem('token')}
}
}
export function fetchData(url) {
return axios.get(`${BASE_URL}${url}`, config());
};
Where I'm struggling are the common errors like 401, 404, etc. Currently, I'm doing this:
export function fetchBrands() {
return function(dispatch) {
dispatch({type:FETCHING_BRANDS});
fetchData('brands')
.then(response => {
dispatch({
type: FETCH_BRANDS_SUCCESS,
payload: response
});
})
.catch(err => {
// deal with errors
});
}
}
But in the catch block, I don't want to have to deal with 401, 404 etc every single time. So I need to be able to deal with those on a more global scale but still have the ability to handle specific errors to the request like server side validation errors for example.
You can use response interceptors as documents in axios documentation.
axios.interceptors.response.use(undefined, function (error) {
if(error.response.status === 401) {
ipcRenderer.send('response-unauthenticated');
return Promise.reject(error);
}
});
other thread with same discussion
You can try to write a function that accepts a function and returns the function with a catch attached. You can even pass an optional secondary argument to execute local catch logic.
This could then be moved to a single file and you can always modify it there.
export function fetchBrand(id) {
return function (dispatch) {
wrapCatch(
fetchData(`brands/${id}`)
.then(response => {
dispatch({
type: FETCH_BRAND_SUCCESS,
payload: response
});
}),
function (err) {
// deal with errors
}
);
}
}
export function wrapCatch(f, localErrors) {
return f.catch(err => {
// deal with errors
localErrors();
});
}
Hope this helps.

Resources