RTKQuery polling with args from store/state - reactjs

I am trying to collect data every 60 seconds, the data I want to collect is keyed by time stamp. Since I only want to collect the data since the last request I am using timestamps as URLSearch params:
query: (args: { id: string; lastFetch: string }) => {
return {
url: `/dashboard/${args.id}/data?start=${
args.lastFetch
}&end=${(new Date()).toJSON()}`,
};
},
Where to begin the 'lastfetch' value is "1899-12-31T00:00:00.000Z" to get all data before this time.
Once data has been received the last fetch field (Which is stored in the Redux Store) is updated to the end time:
dispatch(updateLastFetch({lastFetch: (new Date()).toJSON(), id: args.id}));
The time updates in the store as expected. When the polling interval has elapsed the same arg (originally "1899-12-31T00:00:00.000Z") is used and not the newly dispatched value.
The useQuery has the arg as a value from state that I am updating from the useAppSelector hook from where the lastFetch is being updated. This state value updates and prints to the console.
But this also reinitializes the request causing it to happen again.
Is there a way I can access the store directly from the RTKQuery builder to get the lastFetch value to put directly into the params without having to pass it into the UseQuery?
If not what other way is there to do this type of fetching?
Thank you.
I tried adding a skip value if the start and end date were the same (Which would happen after the first request as there was a re-render with the new 'lastFetch' value) but this then stopped the polling.

Is there a way I can access the store directly from the RTKQuery builder to get the lastFetch value to put directly into the params without having to pass it into the UseQuery?
Yes, if you use a queryFn you will have access to getState through the arguments of the query function. Specifically, the second argument api.
const myApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getData: build.query<Data, string>({
// The args now can be just the string id instead of an object.
queryFn: (args, api, extraOptions, baseQuery) => {
// Select the last fetch timestamp from your state.
const lastFetch = selectLastFetch(api.getState());
// Call the base query like you did before.
return baseQuery({
url: `/dashboard/${args}/data?start=${
lastFetch
}&end=${(new Date()).toJSON()}`,
});
},
}),
}),
})

Related

RTK Query get state from another slice using getState()

I just started on redux yesterday and after reading up on the different libraries, I decided to use the slice route from RTK.
For my async, instead of using createAsyncThunk, I decided to use RTK query and I have a question on the right way to access state from another slice.
slice1 contains some user data for example:
export const initialState: IUserState = {
name: 'example',
id: null,
};
and in my slice2, I have a function that wants to do something like getSomethingByUserId(id) and my current implementation:
interface IApiResponse {
success: true;
result: IGotSomethingData[];
}
const getsomethingSlice: any = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://someapibase',
}),
endpoints(builder) {
return {
fetchAccountAssetsById: builder.query<IApiResponse, null>({
query() {
console.log('store can be called here', store.getState().user.id);
return `/apipath?id=${store.getState().user.id}`;
},
}),
};
},
});
export default getsomethingSlice;
export const { useFetchAccountAssetsByIdQuery } = getsomethingSlice;
As I read somewhere that markerikson mentioned it's not good practice to import the store but to use getState in thunk, I took a look around and see in the documentations that there is getState for query which exist in the onStart unlike thunk which you can access it from it's second parameter.
Do anyone have a onStart implementation for this? Or is importing store acceptable for this?
Generally, we want to prevent people from doing that, which is why you don't have getStore available there (you have at many other places).
You see, RTK-query uses the argument you give into the query to determine the cache key.
Since you don't pass in an argument, the cache key the result would be stored as fetchAccountAssetsById(undefined).
So, you make a first request, state.user.id is 5 and that request is made.
Now, your state.user.id changes to 6. But your component calls useFetchAccountAssetsByIdQuery() and there is already a cache entry for fetchAccountAssetsById(undefined), so that is still being used - and no request is made.
If your component instead would be calling useFetchAccountAssetsByIdQuery(5) and it changes to useFetchAccountAssetsByIdQuery(6), RTK-query can safely identify that it has a cache entry for fetchAccountAssetsById(5), but not for fetchAccountAssetsById(6) and will make a new request, retrieving up-to-date information.
So, you should select that value in your component using useSelector and pass it into your query hook as an argument, not pull it out of the store in your query function.

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.

Redux action on .then promise of another very slow

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?

Mutation with OptimisticResponse doesn't include server response data in second Update

Similar to the question here I have found that when using optimisticResponse and update for a mutation, that the id set from the response of the server is wrong. Furthermore, the id actually gets set by running the optimistic function again.
In the mutation below the refetchQueries is comment out on purpose. I don't want to use that. I want to manage everything through the update only.
Also notice the optimisticResponse id has a "-" prepended to it to prove the optimistic function is run twice:
id: "-" _ uuid(),
Mutation
graphql(MutationCreateChild, {
options: {
// refetchQueries: [{QueryAllChildren, variables: {limit: 1000}}],
update: (proxy, {data: {createChild}}) => {
const query = QueryAllChildren;
const data = proxy.readQuery({query});
data.listChildren.items.push(createChild);
proxy.writeQuery({query, data});
console.log("id: ", createChild.id);
}
},
props: props => ({
createChild: child => {
return props.mutate({
variables: child,
optimisticResponse: () => ({
createChild: {
...child,
id: "-" + uuid(),
__typename: "Child"
}
})
});
}
})
})
The output from the console.log statement is:
id: -6c5c2a28-8bc1-49fe-92e1-2abade0d06ca
id: -9e0a1c9f-d9ca-4e72-88c2-064f7cc8684e
While the actual request in the chrome developer console looks like this:
{"data":{"createChild":{"id":"f5bd1c27-2a21-40c6-9da2-9ddc5f05fd40",__typename":"Child"}}}
Is this a bug or am I not accessing the id correctly in the update function?
It's a known issue, which has now been fixed. I imagine it'll get released to the npm registry soon.
https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/43
https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/d26ea1ca1a8253df11dea8f11c1749e7bad8ef05
Using your setup, I believe it is normal for the update function to be called twice and you are correct that the real id from the server will only be there the second time. Apollo takes the object you return from optimisticResponse and passes it to the update function so your UI can immediately show changes without waiting for the server. When the server response comes back, the update function is called again with the same state (i.e. the state without the optimistic result) where you can reapply the change with the correct value from the server.
As for why the second id you list with the '-' is not the same as the id you see in the chrome dev console, I am not sure. Are you sure that it was actually that request that matched up with that call to console.log?

Resources