Redux action on .then promise of another very slow - reactjs

I have a redux action set up that posts to an external API, this updates a database, and returns the updated results. I then run another function inside to check a database table for new results:
this.props.updateAddTest(payload)
.then((response) => {
if (response.error) {
} else {
let payloadTwo = {
parentTestId: this.state.parentTestId,
bespokeTestId: response.response.testId,
selectedTests: selectedTests,
}
page.props.loadAvailableTests(payloadTwo)
.then((response) => {
page.setState({checkInvalidTests: response.response})
})
}
})
Running this code makes the network response time around 10 seconds - why does it take so long? Running the functions separately, it takes around 200ms. e.g just running:
this.props.updateAddTest(payload);
Why does nesting one redux action inside another slow it down so much?

Related

Vue Fetch Behaving differently on Local vs. Deployed

I feel that I am implementing Vuex Store or async-await incorrectly.
My goal is to pull a set of badges (we call them patches) from my content management service. I need the list to be up to date whenever a new badge is added in the future (which will be infrequent but not never). I don't know how to make a check that will refresh the list whenever the current badge count is different from the number of badges in the cms but that is beside the current problem.
I have an array, rawPatches, in Vuex that should only be built if rawPatches.length <= 0. Once built it should have 60 items pulled from my content management service. This seems to work fine when I do npm run dev on my local machine. However, once pushed to the development site and compiled through Netlify, the array is messed up. When I visit another page and come back to where the length check is the array has an extra 60 items. So when I leave and come back twice then the array has 180 items, and so on and so forth. Also, when I close the window and then come back the incorrect count remains. I guess this means that the Vuex State is cached? I don't know if the length check doesn't get executed or if the array doesn't exist when the check happens but then does exist when new items are added because I'm awaiting the build function. I really have no idea what is going on but I have been trying to sort it out for a few months and am ripping my hair out.
I am using async-await because readPersonalPatches relies on the patches being in Vuex Store.
index.js
export const state = () => ({
rawPatches: [],
});
export const mutations = {
addToArray: (state, payload) => {
state[payload.arr].push(payload.value);
},
}
export const actions = {
async readPatches({ commit, state }) {
console.log('inside of readPatches', state.rawPatches.length);
const patches = await this.$content('patches').fetch();
patches.forEach((patch) => {
commit('addToArray', { arr: 'rawPatches', value: patch });
if (patch.categories) {
if (
JSON.stringify(patch.categories).includes('orbit') ||
JSON.stringify(patch.categories).includes('point')
) {
commit('addToArray', { arr: 'geographicPatches', value: patch });
}
}
if (patch.isSecret) {
commit('addToArray', { arr: 'secretPatches', value: patch });
}
});
console.log('After adding patches', state.rawPatches);
},
}
header component
async fetch() {
console.log('Does this work?');
try {
console.log('length', this.rawPatches.length <= 0);
if (this.rawPatches.length <= 0) {
await this.readPatches({ context: this.$nuxt.context });
}
this.readPersonalPatches();
} catch (error) {
console.log(error);
}
},
Locally, the console reads:
Does this work?
length true
inside of readPatches 0
after reading patches [...]
However, the console is blank on the dev server.
Thank you for any help with this!!

Spread Operator not copying results in React

I am trying to update setState in a for loop, but for some reason state isn't being copied it's just being replaced. There should be 2 clients, instead I am getting one. Can anyone tell me why this is happening? The console.log is returning both clients.
const handleViewClients = () => {
for (let i = 0; i < clients.length; i++) {
console.log(clients[i].clientid);
fetch("http://localhost:3005/all-clients/" + clients[i].clientid)
.then((response) => response.json())
.then((result) => {
console.log(result);
setBarbersClient({
...barbersClient,
client: result,
});
});
}
};
I have also tried this... The console.log is returning what I need
Promise.all(
clients.map((client) =>
fetch("http://localhost:3005/all-clients/" + client.clientid)
)
)
.then((resp) => resp.json())
.then((result) => {
console.log(result.username)
setBarbersClient({
...barbersClient,
client: result,
});
});
Here is the route from the server side
app.get("/all-clients/:clientid", (req, res) => {
db.NewClientsx.findOne({
where: {
id: req.params.clientid,
},
}).then((response) => {
res.json(response);
});
});
There some fundamental concepts of sync vs. async code that you aren't accounting for here. State changing (and fetching) is asynchronous, so it won't run until after this synchronous loop has finished being executed (during which the state value will remain unchanged). Also, it's a bad idea to change state in a loop, for this reason and others.
Fetch all the clients, then do one state change at the end with all the fetched data. You can utilise things like Promise.all and Promise.spread to achieve this. Here's an example of doing multiple fetches then dealing with the results in one batch: How can I fetch an array of URLs with Promise.all?
You're making two distinct mistakes of which either is enough to cause the behaviour you're seeing.
1. You're overwriting the client property.
Every time you call the setter function you're overwriting the previous value of the client property. You'll need some data structure that supports multiple values like a map:
setBarbersClient({
...barbersClient,
clients: {
...barbersClient.clients,
[result.id]: result
},
});
You will need to change your render logic somewhat to accomodate the new data structure.
2. You're using a stale reference.
When you access barbersClient its setter may have already been called with a different value and your reference to it still refers to the value of the previous run of the render function. You can make sure your reference is fresh by using a set state action callback.
setBarbersClient(previousValue => {
...previousValue,
clients: {
...previousValue.clients,
[result.id]: result
},
});
previousValue will never be stale inside the set state action function body.

Cancel query on refetch in apollo

I'm trying to build a small widget with some inputs and cards that contain data which is recalculated on the backend using react and apollo. Cards contain a loader to indicate that request is in progress.
Whenever I change an input, I want to trigger a request to backend, which takes a long time (due to computation) - around 30 seconds.
When you input data - mutation is triggered via one apollo client, but right afterwards it triggers a query to another apollo client, which takes 30 seconds or so.
In that time it is possible to update input again - and in that case, I would like to cancel the previous request and trigger a new one.
This is the query
const {
data,
loading,
refetch,
networkStatus,
} = useCalculationQuery({
widget_id: widgetId,
skip: !isCalcTriggered,
});
Code for useCalculationQuery
function useCalculationQuery({ widget_id, skip }: Options = {}) {
const { teamId } = ... ;
const query = useQuery(QUERY_CALCULATION, {
skip,
fetchPolicy: 'network-only',
variables: {
team_id: teamId,
widget_id,
},
client: apolloCalculations,
});
return query;
}
And this is the fetch function
async function handleCalcFetch() {
if (isCalcTriggered) {
setIsFetchInProgress(true);
await refetch();
setIsFetchInProgress(false);
}
if (!isCalcTriggered) {
setIsCalcTriggered(true);
}
}
As you can see - with isCalcTriggered flag I skip the query execution until handleCalcFetch is called. Then I set the flag, and on every other call except the first I do refetch.
These other flag (isFetchInProgress which is set with setIsFetchInProgress) is to indicate that request is in place, because after calling refetch, networkStatus is always 7 rather than 4. That's issue number one.
Issue number two is that I want to cancel previous requests whenever I hit refetch(), so that I am sure I always get the newest data, which now is not the case.

Timeout on Axios Requests

Our site currently has a filter feature that fetches new data via axios depending on what is being filtered.
The issue is that the filter is done on real time and every change made via react causes an axios request.
Is there a way to put a timeout on the axios request so that I only fetch the last state?
I would suggest using debounce in this case to trigger API call after a specified millisecond of user input.
But just in case you need to add a timeout during axios call, this can be achieved like -
instance.get('/longRequest', {
timeout: 5000
});
The problem has two parts.
The first part is debouncing and is default for event listeners that can be triggered often, especially if their calls are expensive or may cause undesirable effects. HTTP requests fall into this category.
The second part is that if debounce delay is less than HTTP request duration (this is true for virtual every case), there still will be competing requests, responses will result in state changes over time, and not necessarily in correct order.
The first part is addressed with debounce function to reduce the number of competing requests, the second part uses Axios cancellation API to cancel incomplete requests when there's a new one, e.g.:
onChange = e => {
this.fetchData(e.target.value);
};
fetchData = debounce(query => {
if (this._fetchDataCancellation) {
this._fetchDataCancellation.cancel();
}
this._fetchDataCancellation = CancelToken.source();
axios.get(url, {
cancelToken: this._fetchDataCancellation.token
})
.then(({ data }) => {
this.setState({ data });
})
.catch(err => {
// request was cancelled, not a real error
if (axios.isCancel(err))
return;
console.error(err);
});
}, 200);
Here is a demo.
From this axios issue (Thanks to zhuyifan2013 for giving the solution), I've found that axios timeout is response timeout not connection timeout.
Please check this answer
You can also use as a general setting by axios.defaults for all requests:
axios.defaults.timeout = 5000

Strange behavior in react/redux

In my React/Redux app, I make a backend API call to create an entry in a calendar. This is initiated in my handler function which calls the action creator.
Once this initial step is done, I check to see if the entry the user has just created has the same date as the current date my calendar component showing. If so, I call the backend API to get calendar events. I do this to refresh the calendar.
As I step through the process, everything seems to be working fine BUT my calendar does not show updated data.
Here comes the weird part: as I step through this process, everything works and the calendar updates fine. In other words, if I somehow slow down the process, everything seems to be working perfectly fine.
If I don't slow down the process, the calendar fails to update. There are no errors. And as I said, as I step through the process, I see that the API returns correct data, action creator to SET_CALENDAR_EVENTS gets called which then calls the reducer and the reducer sets the data.
Like I said, there are no problems except if I let it happen without slowing down the process, the calendar doesn't update.
Any idea what's causing this? Any suggestions?
My handler function code looks like this:
clickHandleCreateEvent(event) {
// Call API
this.props.actions.createEvent(event);
// Get active date
const activeDate = this.props.activeDate;
if(activeDate === event.eventDate) {
this.props.actions.getCalendarEvents(activeDate);
}
}
UPDATE:
Here's my getCalendarEvents function:
export const getCalendarEntries = (calendarId, date) => {
// Create get calendar entries object
var request = {
id: calendarId,
date: date
};
// Get calendar entries
return (dispatch) => fetch('/api/calendars/entries', fetchOptionsPost(request))
.then((response) => {
if (response.ok) {
// Got events
parseJSON(response)
.then(entries => {
dispatch(setEvents(entries))
})
.then(() => dispatch(setCalendarIsLoading(false)))
} else {
// Couldn't get data
dispatch(setBadRequest(true))
}
})
}
Since both createEvent and getCalendarEvents are async functions involving network communication there is no guarantee which request reaches the server first. So you might read old data while createEvent request were still travelling over the wire.
To avoid this you need to synchronize both requests ie call getCalendarEvents after the server has responded ok to createEvent request.
clickHandleCreateEvent(event) {
// Call API
return this.props.actions
.createEvent(event);
.then(() => {
// Get active date
const activeDate = this.props.activeDate;
if(activeDate === event.eventDate) {
return this.props.actions.getCalendarEvents(activeDate)
}
})
}

Resources