How to merge between RTK query and redux toolkit - reactjs

I have a redux slice called pendingPost where I add some field to it like car_mileage using my reducers functions and save all this inside my pendingPost slice. Then submit using the data inside the pendingPost reducer
const pendingPostReducer = createSlice({
name: 'pendingPost',
initialState,
reducers: {
...
addPropertyToPendingPost: (state, action) => {
state.savedData = { ...state.savedData, ...action.payload };
},
Also I have postsAPI where I use rtk query to get All Posts, user Posts, ...
export const postsApi = createApi({
baseQuery: fetchBaseQuery({
baseUrl: API_URL,
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query({
query: (body) => ({
url: `post/filter`,
method: 'POST',
body: body,
}),
providesTags: (result) =>
result
? [
...result.data.map(({ id }) => ({ type: 'Post', id })),
{ type: 'Post', id: 'LIST' },
]
: [{ type: 'Post', id: 'LIST' }],
What I want to do is combine both of these where when I create post I want to do mutation and invalidate. How can I achieve this ?
I tried to search for a way to add save some fields inside RTK query but didn't find a way to achieve that, I guess rtk query is used only for caching and queriess

Your question has two parts.
I think you have a post scenario and you want to add another post to the list and update the posts.
for the first part
I assume you store the posts inside of postPending.(if this slice reducer is for the post so postPending is not a good name for this slice you should name it postSlice and inside of it do everything about the post) and show list of post on a page based on post pending.
in this case, you should go for createAsyncThunk instead of the RTK query. because as you guessed the RTK query purpose is caching the queries.
I don't know this will help you or not but you can dispatch RTK query outside of ApiSlices like so:
dispatch(ApiSlice.endpoints.getPosts.initiate())
for the second part:
I create an example for you in here.
basically, you need to create ApiSlice using RTK query which handles get post. so you have to follow these steps:
1- create API slice
2- create GET query endpoint for fetching the list.
3- use tagTypes to tell RTK query I have these tags.
4- use providesTags for each endpoint you create to tell RTK query I have these variants of tags for this endpoint.
5- when you want to create a POST, PUT, PATCH, or DELETE request to the server you literally case a change to the available list so you need mutations instead of query.
6- in mutations, you will use invalidatesTags to tell RTK query got and find in tags I've already provide for you and remove them if they have the same identity as the tags I invalidate in invalidatesTags.
7- invalidating tags makes the RTK query find out it must re-fetch the query and update the cache.
and you do not need to store posts somewhere else. as you can see I use the query hook in 2 different components and I only make a request once.
since the RTK query knows how many subscriptions you have to the same hook and if cache data is available for that query it will return it to the hook and will not create another request. in other words RTK query Hook will play a role as your postPending slice so don't have to store the data in two places.

Related

How to use RTK Query's 'upsertQueryData' to add a new entry to the cache?

I have a GraphQL query setup using the Redux Toolkit's "RTK Query" data fetching functionality. After a mutation related to this query I want to add the returned data from the mutation to be added to the cache without calling the query to the server again. I used the thunk action creator upsertQueryData from the API slices utilities for this. (Reference Documentation).
So far I was only able to overwrite the complete cache collection collection related to the Query but did not find a way to just add 1 entry. Perhaps someone knows what I'm doing wrong?
The GraphQL Query, that is working fine. It returns a collection of 'sites'.
endpoints: (builder) => ({
getSites: builder.query({
query: () => ({
document: gql`
query MyQuery {
sites {
id
name
description
}
}
`,
}),
}),
...
The mutation with usage of upsertQueryData. This overwrites the whole collection of 'sites' of the cache instead of used adding 1 site. To be clear: When sending the mutation I don't have an id yet, that is returned by the server through the mutation callback.
createSite: builder.mutation({
query: ({name}) => ({
document: gql`
mutation createSite {
createSite(
name: "${name}"
description: "The workspace where Peter works from home in Dordrecht",
) {
site {
id
name
description
}
}
}
`
}),
async onQueryStarted({}, { dispatch, queryFulfilled }) {
const { data } = await queryFulfilled;
const newSiteEntry = data.createSite.site;
sites.util.upsertQueryData('getSites', { newSiteEntry.id }, newSiteEntry);
}
I expect to that it adds 1 date object to the site cache object instead of overwriting it. So you will get something like this in the cache:
sites: [
{id: '1', name: 'Existing site 1', description: 'description 1'},
{id: '2', name: 'Existing site 2', description: 'description 2'},
{id: '3', name: 'New site', description: 'new description'},
]
You generally have a wrong concept of the cache here. Since your getSites endpoint takes no argument and you probably only ever call useGetSitesQuery(), there is only ever one cache entry for that (called getSites(undefined)), and you want to update that existing cache entry with additional lines.
upsertQueryData is for overwriting that whole cache entry with a new value, or in your case, creates completely unrelated cache entries that you will never read from - not what you want to do.
As a result, you want to updateQueryData for that one existing cache entry instead:
dispatch(
api.util.updateQueryData('getSites', undefined, (draft) => {
draft.sites.push(newSiteEntry)
})
)
Keep in mind though that generally we recommend using providesTags/invalidatesTags to automatically refetch other endpoints instead of manually doing optimisistic updates on them.

How to clear & invalidate cache data using RTK Query?

I was facing a problem for sometime, that was I'm unable to clear cache using RTK query.
I tried in various ways but cache data is not clear.
I used invalidatesTag in my mutation query and it called the api instantly. But in this case I want to refetch multiple api again, but not from any rtk query or mutation. I want to make the api call after some user activity like click.
How can I solve this problem?
I made a separate function where I return api.util.invalidateTags(tag) or api.util.resetApiState().
this is my code-snipet:-
` const api = createApi({.....})
export const resetRtkCache = (tag?: String[]) => {
const api =
if (tag) {
return api.util.invalidateTags(tag)
} else {
return api.util.resetApiState()
}
}`
& I called it using dispatch method from other files
`const reloadData = () => {
dispatch(resetRtkCache())
}`
but here cache data is not removed.I think dispatch funtion is not working. I don't see the api call is being sent to server in the browser network.
But in this case I want to refetch multiple api again, but not from
any rtk query or mutation. I want to make the api call after some user
activity like click. How can I solve this problem?
So if I understood correctly what you want to achieve is to fetch some api that you have in RTK only after some kind of user interaction?
Can't you just define something like this?
const { data } = useGetYourQuery({ skip: skipUntilUserInteraction })
Where skipUntilUserInteraction is a component state variable that you will set to true and update to false based on the user interaction you need? (e.g. a click of a button).
So essentially on component render that specific endpoint will be skipped but will be fetched after the interaction that you want will happen?
wow, you actually asking so many questions at once. but I think you should definitely read the documentation because it covers all the questions you have.
so trying to answer your questions one by one.
I used invalidatesTag in my mutation query and it called the api instantly.
invalidating with Tags is one of the ways to clear the cache.
you should first set the tagTypes for your API then use those tags in mutation queries and tell the RTK query which part of entities you want to clear.
I want to refetch multiple APIs again
you can customize the query inside of a mutation or query like this example and by calling one function query you can send multiple requests at once and if you want to fetch the API again after the cache removed you do not need to do anything because RTK query will do it for you.
I want to make the API call after some user activity like click
every mutation gives u a function that you can pass to onClick like below:
import { use[Mymutation]Mutation } from 'features/api';
const MyComponenet() {
const [myMutationFunc, { isLoading, ...}] = use[Mymutation]Mutation();
return <button type='button' onClick={myMutationFunc}>Click for call mutaion</button>
}
and remember if you set providesTags for your endpoint which you were defined in tagTypes by clicking on the button and firing up the myMutationFunc you will be clearing the cache with those tags.
and if you looking for an optimistic update for the cache you can find your answer in here.
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}

Apollo client v3 not caching query results with useQuery

I am using apollo v3 with a create-react app. I fire a query with useQuery and expect results to be cached, but they are not.
In my App.jsx, I have:
const client = new ApolloClient({
uri: `${api}/graphql`,
cache: new InMemoryCache()
})
I wrap my app with the ApolloProvider.
I have a provider that uses this graphql query to fetch a list of users:
const USERS_QUERY = gql`
query GetUsers {
users {
id
email
fullName
}
}
`
The query works, when I inspect the apollo tab in chrome devtools, I see nothing in the cache section.
My questions:
Why are the results not cached if I am using the useQuery from #apollo/client?
const { loading, error, data } = useQuery(USERS_QUERY)
I thought results should be cached automatically.
I also tried to add a type policy:
cache: new InMemoryCache({
typePolicies: {
Users: {
keyFields: ['id']
}
}
})
But I think I use this if I want to normalise with a different key the cache, or if I want to decide how to merge new data by myself. But first I need to have the data in my cache
As far as I know, this is because your query does not have any input arguments / variables, so the cache does not know what item to get from the cache when the query is called again. From what I understand, the cache is only used when a specific piece of data is looked for with an ID; otherwise, if it is a generic query, the data might have changed and so is not cached.

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.

Prevent Graphiql console from issuing multiple introspection queries

I am using the Graphiql component to render a console and want to fetch the schema via an introspection query. The problem is that if the component re-renders before the first introspection query is resolved (say a modal is opened for example), a second introspection query is fired off. Given that these queries are expensive for the backend, I'd like to avoid this.
Is there a way to avoid multiple introspection queries?
The GraphiQL component accepts a schema prop:
schema: a GraphQLSchema instance or null if one is not to be used. If undefined is provided, GraphiQL will send an introspection query using the fetcher to produce a schema.
You can use getIntrospectionQuery to get the complete introspection schema, fetch the introspection result and then use it to build a schema.
const { getIntrospectionQuery, buildClientSchema } = require('graphql')
const response = await fetch('ENDPOINT_URL', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: { query: JSON.stringify(getIntrospectionQuery()) },
})
const introspectionResult = await response.json()
const schema = buildClientSchema(introspectionResult.data)
Do this before you render the component and then just pass in the schema as a prop. If your schema isn't going to change, you can also just save the introspection result to a file and use that instead of querying the server.

Resources