I'm developing a react app using RTK Query & AppSync (graphQL).
I tried a query request as follows, but always the redux status is "rejected" saying "Cannot read properties of undefined (reading 'filter')" (Please check a pic below).
It seems the request itself is successfully done (200), so I guess it is due to the geaphQL client.
Redux Toolkit without RTK Query works as expected.🧐
Please help😭
RTK Query (rejected)
import { createApi } from '#reduxjs/toolkit/query/react';
import { graphqlRequestBaseQuery } from '#rtk-query/graphql-request-base-query';
import { API } from 'aws-amplify';
import { GRAPHQL_AUTH_MODE } from '#aws-amplify/api-graphql/lib/types';
import { listSurveyTitles } from 'src/graphql/queries';
import aws_exports from 'src/aws-exports';
API.configure(aws_exports);
export const surveyTitlesApi = createApi({
reducerPath: 'surveyTitles',
baseQuery: graphqlRequestBaseQuery({
url: '/graphql',
}),
endpoints: (builder) => ({
fetchSurveyTitles: builder.query({
query: ({ limit = 2147483647, params }) => ({
document: API.graphql({
query: listSurveyTitles,
variables: { limit, ...params },
authMode: GRAPHQL_AUTH_MODE.API_KEY,
}),
}),
}),
}),
});
export const { useFetchSurveyTitlesQuery } = surveyTitlesApi;
Slice with Redux Toolkit not RTK (fulfilled)
export const fetchSurveyTitles = createAsyncThunk(
'planner/fetchSurveyTitles',
async ({ limit = 2147483647, ...params }, thunkAPI) => {
try {
return await API.graphql({
query: listSurveyTitles,
variables: { limit, ...params },
authMode: GRAPHQL_AUTH_MODE.API_KEY,
});
} catch (e: any) {
return thunkAPI.rejectWithValue(e);
}
}
);
Showing same request payloads for the both approaches (200)
The way you have written that there, the return value of your query function would be fed into graphqlRequestBaseQuery, which in turn calls graphql-request - but you already have made your request and everything by using the amplify client.
If you want to use the amplify client, you don't need the graphqlRequestBaseQuery.
In that case, just use queryFn instead of query:
endpoints: (builder) => ({
fetchSurveyTitles: builder.query({
async queryFn ({ limit = 2147483647, ...params }) {
try {
const data = await API.graphql({
query: listSurveyTitles,
variables: { limit, ...params },
authMode: GRAPHQL_AUTH_MODE.API_KEY,
});
// it is important that the object you return either has the form `{data}` or `{error}`
return { data }
} catch (error: any) {
return { error }
}
Related
I have a project already configured with Supabase and using Redux-Toolkit. I have never used RTK-Query and I am just learning but after reading in the docs and looking for similar questions I have created a supabaseApi.js file which looks like:
import { createApi, fakeBaseQuery } from '#reduxjs/toolkit/query';
export const supabaseApi = createApi({ baseQuery: fakeBaseQuery(),
endpoints: (builder) =({
getStudents: builder.query({
queryFn: async () ={
const students = await supabase
.from('students')
.select()
return {students, error}
}
})
}) })
export const { useGetStudentsQuery } = supabaseApi;
However, when I call the useGetStudentsQuery() to get the students table I receive an error in the console telling me that "useGetStudentsQuery is not a function". I also noticed that when I begin to write the useGet function, it seems it hasn't been automatically created as I don't get the Visual Studio hints.
What is it that I am doing wrong? Thanks.
A few things:
to have the hooks, you have to import from '#reduxjs/toolkit/query/react';
the queryFn has to return either { data } or { error }, not { student }
your code does not seem to have a error variable, but tries to return it.
so:
import { createApi, fakeBaseQuery } from '#reduxjs/toolkit/query/react';
export const supabaseApi = createApi({ baseQuery: fakeBaseQuery(),
endpoints: (builder) =({
getStudents: builder.query({
queryFn: async () ={
const students = await supabase
.from('students')
.select()
return { data: students }
}
})
}) })
export const { useGetStudentsQuery } = supabaseApi;
I simply want to use RTK to run an API request as part of an onSuccess function and await the response. I was doing this in Axios no problem but I'm trying to replace my Axios calls with RTK and having trouble using the LazyQuery.
I'm getting an exception
useLazyQuery is not a function or its return value is not iterable
My API:
const linkAPI = overweightRTK.injectEndpoints({
tagTypes: ['Link'],
endpoints: (builder) => ({
createLink: builder.mutation({
query: (body) => ({
url: `/links/`,
method: 'POST',
body,
invalidatesTags: ['Link']
})
})
}),
overrideExisting: false
});
export const { useCreateLinkMutation} = linkAPI;
My component:
const [trigger, result, lastPromiseInfo] = OverweightRTK.endpoints.createLink.useLazyQuery()
const onSuccess = async (data) => {
const createLinkRequest = {
payload: data
};
trigger({ createLinkRequest }, { skip: !isSubscriber })
.unwrap()
.then(res => {
if (res.status === 200) {
setSuccessMessage('Success, your account linked');
}
})
.catch((error) => console.log(error));
};
I figured it out. Apparently for mutation endpoints you don't need to use the lazyQuery(). Mutation queries by default return a similar response as lazyQuery and already return the tuple containing the trigger.
Working code: (This replaces the first line in my component code, nothing else needed to be changed)
const [trigger, result, lastPromiseInfo] = useCreateLinkMutation();
Useful docs on the subject: https://redux-toolkit.js.org/rtk-query/usage/mutations
I have 2 completely independent components without any parent-child relationship being displayed on a single page.
Component 1 : Makes an API call fetches some records and display it in a table having server side-pagination
Component 2 : Contains a form, when the user submits the form the data in the component 1 needs to be refetch-ed through the backend.
Since I am using fetchBaseQuery to query the data, I believe I need to invalidate the cache in order to make the API call in the component 1.
I tried refetch() to fulfil that requirement but got no luck. I also tried setting the cache timeout using keepUnusedDataFor that too didn't work. Also, tried to do something with the tags, but for that I will have to use mutation instead of query and I am not sure how mutation is useful as per my use case
Here's some of the code :
component1.tsx
let { data, error, isSuccess, isError, isFetching, refetch } = useGetQuery(request, { skip});
const records = data?.records;
React.useEffect(() => {
if (records) {
// set records within table
}
}, [records]);
useGetQuery.ts
const extendedApi = mainApi.injectEndpoints({
endpoints: (builder) => ({
getQuery: builder.query<response, request>({
query: (request?: request) => ({
url: "someURL",
body: request,
method: "POST",
}),
providesTags: ["Requests"],
}),
}),
overrideExisting: true,
});
export const { useGetQuery } = extendedApi;
component2.tsx
let [trigger, data] = useSubmitFormMutation();
const submitForm = (e) => {
e.preventDefault();
trigger(// Some Object);
}
React.useEffect(() => {
if (isSuccess) {
updateRefreshRecords(true); // setting the hook to true to make an API call in component 1
}
}, [isSuccess]);
useSubmitFormMutation.ts
const extendedApi = mainApi.injectEndpoints({
endpoints: (builder) => ({
submitForm: builder.mutation<response, request>({
query: (request?: request) => ({
url: "some_other_url",
body: request,
method: "POST",
}),
invalidatesTags: ["Requests"],
}),
}),
overrideExisting: false,
});
export const { useSubmitFormMutation } = extendedApi;
mainAPI.ts
export const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (args, api, extraOptions) => {
const { mainApiUrl } = (api.getState() as RootState).settings.endpoints;
const rawBaseQuery = fetchBaseQuery({
baseUrl: mainApiUrl,
prepareHeaders: (headers, { getState }) => {
// Use getState to pull the jwtToken and pass it in the headers to the api endpoint.
const { jwtToken } = (getState() as RootState).auth;
headers.set("authorization", jwtToken);
return headers;
},
});
return rawBaseQuery(args, api, extraOptions);
};
export const mainApi = createApi({
reducerPath: "mainApi",
baseQuery: dynamicBaseQuery,
endpoints: () => ({}),
tagTypes: ["Requests"],
});
store.ts
export const store = configureStore({
reducer: {
// other reducers
[localApi.reducerPath]: localApi.reducer,
[mainApi.reducerPath]: mainApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
})
.concat(localApi.middleware)
.concat(mainApi.middleware),
});
Can you please help me how can I invalidate the cache as per my use case.
Any help would be highly appreciated
Thanks
You can just add invalidatesTags to your mutation and that should refresh the query:
const extendedApi = mainApi.injectEndpoints({
endpoints: (builder) => ({
submitForm: builder.mutation<response, request>({
query: (request?: request) => ({
url: "some_other_url",
body: request,
method: "POST",
}),
invalidatesTags: ["Requests"]
}),
}),
overrideExisting: false,
});
No need for manual refetching or keepUnusedDataFor.
If that doesn't work, double-check that you added the api's middleware to the middlewares in your configureStore
Simply change your submitForm endpoint to mutation type and invalidate "Requests" tag on this endpoint. This way you don't have to use updateRefreshRecords.
You can then remove below useEffect in Component1.tsx
React.useEffect(() => {
if (refreshRecords) {
refetch();
}
}, [refreshRecords]);
and also remove keepUnusedDataFor: 5, from getQuery endpoint
I am not sure how mutation is useful as per my use case
When form is submitted, you are either creating or updating some data on backend. So, mutation is the right type of endpoint here. Use query type endpoint when you want to fetch some data from backend.
I am trying to achieve delete and add functionality in react using RTK-Query with firestore. It sound might be weird that I am using RTK Query to perform firestore operation in React. So, I have written service API file to delete and add operation with firestore. So, whenever I try to delete or add data in firestore with RTK Query, so I am getting some weird error in my console. However, when I refresh the application then I am seeing the updated data on my app after performing the add/delete operation. For some reason, initially it's not providing me correct result due to below error but after page refresh I am getting the updated value from firestore in my react app.
Here is code for service API
import { createApi, fakeBaseQuery } from "#reduxjs/toolkit/query/react";
import {
addDoc,
collection,
deleteDoc,
doc,
getDocs,
onSnapshot,
serverTimestamp,
} from "firebase/firestore";
import { db } from "../firebase";
export const contactsApi = createApi({
reducerPath: "api",
baseQuery: fakeBaseQuery(),
tagTypes: ["Contact"],
endpoints: (builder) => ({
contacts: builder.query({
async queryFn() {
try {
const userRef = collection(db, "users");
const querySnapshot = await getDocs(userRef);
let usersData = [];
querySnapshot?.forEach((doc) => {
usersData.push({
id: doc.id,
...doc.data(),
});
});
return { data: usersData };
} catch (err) {
console.log("err", err);
return { error: err };
}
},
providesTags: ["Contact"],
}),
addContact: builder.mutation({
async queryFn(contact) {
try {
await addDoc(collection(db, "users"), {
...contact,
timestamp: serverTimestamp(),
});
} catch (err) {
return { error: err ? err : null };
}
},
invalidatesTags: ["Contact"],
}),
deleteContact: builder.mutation({
async queryFn(id) {
try {
await deleteDoc(doc(db, "users", id));
} catch (err) {
if (err) {
return { error: err };
}
}
},
invalidatesTags: ["Contact"],
}),
}),
});
export const {
useContactsQuery,
useAddContactMutation,
useDeleteContactMutation,
} = contactsApi;
store.js file
import { configureStore } from "#reduxjs/toolkit";
import { contactsApi } from "./services/contactsApi";
import { setupListeners } from "#reduxjs/toolkit/query";
export const store = configureStore({
reducer: {
[contactsApi.reducerPath]: contactsApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}).concat(contactsApi.middleware),
});
setupListeners(store.dispatch);
A queryFn has to always return an object with either a data property or an error property - in your case you only do that in the error case.
Try adding a return { data: 'ok' } if you don't have any better idea.
I'm new in RTK Query and I'm struggling with a use case I have to implement.
Scenario:
I have to merge the results from two API calls: the first API call is a private API call while the second one is a public API call. I need to merge the responses from these two APIs and write the computed result into the RTK cache so the UI can update accordingly.
Problem:
I'seeing that as soon as the await queryFullfilled is invoked, RTK Query immediately write into its cache the response from that API call and then when I make my calculation and try to update the RTK cache with apiSlice.util.updateQueryData the cache will change again. That's means that the UI will render twice, the first time using a wrong value (an array of persons) and the second time with the correct value (the JSON composed by ids and entities).
Question:
Is there a way to have just 1 write into the RTK cache so I can have just the computed value I need ? Because what is happening is that for some instances I'm having into the cache an array while I need the {ids: [...], entities: {}} JSON.
import { createEntityAdapter } from '#reduxjs/toolkit';
import axios from 'axios';
export const personsAdapter = createEntityAdapter();
const permitsInitialState = personsAdapter.getInitialState();
export const apiSlice = myServiceApi.injectEndpoints({
endpoints: (builder) => ({
getPersons: builder.query({
query: () => ({ url: '/persons', method: 'get' }),
onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
try {
// Resolving the private API call
const { data: persons } = await queryFulfilled;
// Just a random public API call
const { data: todos } = await axios('https://jsonplaceholder.typicode.com/todos');
const enhancedPersons = /** Here the logic that merge the todos and the persons */
const state = personsAdapter.setAll(permitsInitialState, enhancedPermits);
dispatch(
apiSlice.util.updateQueryData('getPersons', _, (draft) => {
Object.assign(draft, state);
})
);
} catch (e) {
console.error(e);
}
},
}),
}),
});
That is one of the use cases of queryFn: Performing multiple requests with a single query
import {
createApi,
fetchBaseQuery,
FetchBaseQueryError,
} from '#reduxjs/toolkit/query'
import { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUserPosts: build.query<Post, void>({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random')
if (randomResult.error) throw randomResult.error
const user = randomResult.data as User
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data
? { data: result.data as Post }
: { error: result.error as FetchBaseQueryError }
},
}),
}),
})