Redux Toolkit RTK Query call endpoints in queryFn - reactjs

I'm using an endpoint with queryFn instead of query to perform many requests. Is there a way to calling endpoints that are already define instead of using fetchWithBQ ?
Here is an example.
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({
baseUrl: "url",
}),
endpoints: (builder) => {
return {
device: builder.query<Device, string>({
query: (id) => `devices/${id}`, // repeat 1
}),
deployments: builder.query<Deployment[], string>({
queryFn: async (arg, _api, _extraOptions, fetchWithBQ) => {
// I would preferred to call the device endpoint directly.
// It will prevent to repeat the url and get cached data.
const result = await fetchWithBQ(`devices/${arg}`); // repeat 2
return ...
},
}),
};
},
});

No, at the moment that is not possible because it would add a tracking of "what depends on what else" to the whole things and that would get very complicated to manage internally.
You would usually do dependent queries just by using two useQuery hooks. And for abstraction of course you could combine those into a custom hook.
const useMyCustomCombinedQuery = (arg) => {
const result1 = useMyFirstQuery(arg)
const result2 = useMySecondQuery(result1.isSuccess ? result1.data.something : skipToken)
return {result1, result2}
}

Related

RTK query based on data from another request

I have some rtk query, that query data based on response of another request ( with axios )
const { dat } = useGetDataQuery({
keys // it comes from another request
})
export const someApi = createApi({
reducerPath: 'someApi',
baseQuery,
endpoints: (builder) => ({
getData: builder.query<
any,
{ keys: string[] }
>({
query: (arg) => {
const { keys } = arg
return {
url: '/some_endpoint',
params: {
keys,
},
}
},
}),
}),
})
And in this case data from previous request it's keys.
Is it possible make request inside query and use this data for my query, instead of doing
this request outside and pass data as params?
I wouldn't recommend it. Theoretically it is possible, using queryFn, as you have access to getState there - but if that state value would ever change, that wouldn't re-execute your query. It's really best to just put in dynamic values as argument.

FetchbaseQuery invalidate cache

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.

Redux Toolkit RTK fetchBasedQuery.prepareHeaders not working on the first call

I have this apiSlice:
export const apiSlice = createApi({
baseQuery: fetchBaseQuery({
baseUrl: `${baseUrl}${apiBasePath}`,
prepareHeaders: prepHeaders,
}),
endpoints: (builder) => ({
getPersonById: builder.query({
query: (personId) => ({
url: `person/${personId}`,
}),
}),
}),
});
const prepHeaders = (headers, { getState }) => {
const { token } = getState().auth;
headers.set('X-Request-Id', uuidv4());
headers.set('X-Client-Id', 'xxx');
headers.set('X-Client-Version', '1.0.0');
headers.set('Content-Type', 'application/vnd.api+json');
headers.set('Accept', 'application/vnd.api+json');
headers.set('Authorization', `Bearer ${token}`);
return headers;
};
Which is being used in this component:
const PersonDetails = () => {
const { personId } = useParams();
const { data, error, isLoading, isFetching } =
useGetPersonByIdQuery(personId);
return (<div>...render logic removed...</div>)
}
On load of the ReactJS page, it triggers the apiSlice right away since the default landing page renders the PersonDetails component.
1.But the first call has missing headers:
2.Refetching it via the refetch function now contains the headers:
UPDATE #1: Just observed that the Unit Test works fine and has the headers; so its really just when the App is actually running in the browser when this weird behaviour is occurring, what could it be?
UPDATE #2: Looks like it might be related to MirageJS when its turned on to as to intercept calls and return mock data. I observed that if I turn it off on startup, the initial call to the actual API has the headers. So I guess the question is now why is MirageJS causing this issue?

How to implement multiple api call in a single query with RTK query

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 }
},
}),
}),
})

How to use Redux RTK Query with Supabase

Does anyone know how to use the Supabase Query pattern combined with RTK Query like for example https://dev.to/sruhleder/using-react-query-with-supabase-a03.
While I haven't used Supabase at all, it looks like it provides a Promise-based async request API.
In that case, you could use it with RTK Query's queryFn endpoint option, which lets you write your own arbitrary async logic and return whatever data you want.
While I haven't tested this code, a translation of the React Query + Supabase example to RTKQ might look like:
import { createApi, fakeBaseQuery } from '#reduxjs/toolkit/react';
const supabaseApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: (builder) => ({
getTodos: builder.query({
queryFn: async () => {
const {data, error} = await supabase
.from('todo')
.select('id, name')
.eq('done', false)
return data;
}
})
})
})

Resources