Redux-toolkit: RTKQ: Mass rejected queries

Created on 17 Jun 2021  Â·  10Comments  Â·  Source: reduxjs/redux-toolkit

I've noticed many rejected queries in console log

action api/executeQuery/rejected
[Log]  action     – {type: "api/executeQuery/rejected", payload: undefined, meta: Object, …} (main.js, line 136707)
{type: "api/executeQuery/rejected", payload: undefined, meta: Object, error: {name: "ConditionError", message: "Aborted due to condition callback returning false."}}Object

Since I cannot use selectors with RTK Query(#1182) I subscribe each small component to same query with selectFromResult option to retrieve my data slice. To be more specific I load all job applications with one query(GET /applications?campaignId=123) then I have long datatable with each application as a row. Each row contains few micro forms eg. candidate name to enter name and push enter to post data.
So each microform subscribed to the same GET /applications?campaignId=123 request then it finds application by id and reads one field(name) from searched application item. Because of it I see 1400 rejected requests in console and everything becomes unresponsive. I think even if I turn logger off it still gonna overload redux state with thousands of dispatched actions.

Seems like I need to find another way to select data from API queries or pass query results from root element to child over and over again.

Could it be a bottleneck if I subscribe each list item to query hook?

rtk-query

All 10 comments

You could also use the useQueryState hook or endpoint.select to create a selector.

Generally, it is preferrable to use the useQuery hook if your component could ever be mounted without an ancestor triggering the request. But of course, doing so 1400 times is a tad excessive and also that does not seem to be the case here.

Even then, subscribing 1400 components to the store in general is not free, as would be passing this down via props. Have you looked into list virtualization?

You could also use the useQueryState hook or endpoint.select to create a selector.

Thanks, Lenz. Already found docs about useQueryState hook. Btw endpoint.select doc looks really scary without vanilla JS example. It might be really easy for you TypeScript guys, but I reconsidered once I need to understand your function declaration. 😆

Sorry to be pain in the ass, but useQueryState hook doesn't work. IDE and compiler says that hook doesn't exist.
console.log(api) also doesn't show it:

[Log] find hooks (main.3a07878eaf2210db71cf.hot-update.js, line 80)
Object

endpoints: {listCampaigns: Object, listApplications: Object}

enhanceEndpoints: function(_e)

injectEndpoints: function(inject)

internalActions: {onOnline: function, onOffline: function, onFocus: function, onFocusLost: function, middlewareRegistered: function, …}

middleware: function(mwApi)

reducer: function(state, action)

reducerPath: "api"

useLazyListApplicationsQuery: function(options)

useLazyListCampaignsQuery: function(options)

useListApplicationsQuery: function(arg, options)

useListCampaignsQuery: function(arg, options)

usePrefetch: function(endpointName, defaultOptions)

util: {patchQueryData: function, updateQueryData: function, prefetch: function, resetApiState: function, …}

Version of RTK is "@reduxjs/toolkit": "^1.6.0"

Even then, subscribing 1400 components to the store in general is not free, as would be passing this down via props. Have you looked into list virtualization?

Damn, I thought React and Redux has been designed for it :smile: I loose all the magic if I cannot create smart reusable components. But maybe you right, subscribe each div to Redux store might be too much at current tech level.

Hmm. I'm not sure we actually expose those "internal" hooks on the root API slice object itself. They may only be accessible on the endpoints: myApiSlice.endpoints.someEndpoint.useQuerySTate.

They are not "internal", just not super-exposed :)
We do not expose those not-so-often-used hooks on the api object itself, but just on api.endpoints.endpointName to not totally clutter the api object with hooks.

As for the TS: yeah, it's a tradeoff. I think we show it in the svelte example, but we could add it there somewhere...

Found it, now useQueryState hook works very well, thank you. Since I have two methods to retrieve query cache result(selectFromResult and endpoint.select) which can be faster? Should I write selectors for endpoint.select or performance will be the same as with selectFromResult callback?

I read mutation docs chapter for two days already. I do understand that you developed the package with canonical REST API in mind. That's why your mutation endpoints cannot contain providesTags because usually it's 2xx Ok response without body. But what if my response contains updated resource and I don't want to refetch but update cache right away? Do I need to use onCacheEntryAdded lifecycle callback with utils? I cannot add providesTags to mutation endpoint even if my response has updated data, right? I also shouldn't add invalidatesTags prop if I update cache manually within endpoints callback?

Sorry for offtopic regarding mutations, don't want to open new issue.

We do not expose those not-so-often-used hooks on the api object itself, but just on api.endpoints.endpointName to not totally clutter the api object with hooks.

That should be added to docs if you don't want to answer the same question over and over again.

Should I write selectors for endpoint.select or performance will be the same as with selectFromResult callback?

Probably pretty much the same

As for the provides question.

"tags" are really just "labels" that you slap onto something. They provide only the information "what kind of thing has been received here" so that that information can later be used to trigger a selective re-fetch. Triggering mutations automatically would be dangerous, since we assume those change something on the server, so we will never do that.
It is not a "data consolidation mechanism" of any form. RTKQ is not a normalized cache and does not plan to be it.

If you want to update data manually, you are right, that is done via lifecycle.

If you want to update data manually, you are right, that is done via lifecycle.

Works great so far. Is there any way to get response body within lifecycle callbacks(onCacheEntryAdded)? I want to add created item manually to cache without refetch since I already got data from a POST call.

const {data, meta} = await cacheDataLoaded

But you can only update existing cache entries, not generate new ones

const {data, meta} = await cacheDataLoaded

But you can only update existing cache entries, not generate new ones

Not helpful in my case, already checked. I guess cacheDataLoaded works with queries only while I want to get response of POST mutation request.

Hmm, in a mutation I would go with the onQueryStarted callback. Cache entries there have no consolidation/overlap anyways, and within that you should be able to access the result.

Also, both queries and mutations have cacheDataLoaded - I just checked, we even have a unit test for that https://github.com/reduxjs/redux-toolkit/blob/master/src/query/tests/cacheLifecycle.test.ts#L81

You only might not reach that if the value is removed from the cache before it resolves, which might happen with many mutations in succession and is the reason why I would go with onQueryStarted instead.

Was this page helpful?
0 / 5 - 0 ratings