Pass callback to transformResponse in RTK Query endpoint - redux-toolkit

I have a RTK query API
export const api = createApi({
baseQuery: graphqlRequestBaseQuery({
// ...
}),
endpoints: (builder) => ({
getPeople: builder.query({
query: (params) => ({
document: params,
}),
transformResponse: (response) => {
// I wanna call a function here that I'm passing to this endpoint
},
}),
}),
});
and in my UI
const { data: people, isLoading: isLoadingPeople } = useGetPeopleQuery(people);
I'm wondering how can I pass a callback function from the UIs hook to the endpoint so that I can call it from transformResponse.

Related

Sequential execution / queue of endpoint of createApi

One of my endpoints should be called one by one, since the backend doesnt support multiple parallel requests. Ive tried two ways to do it:
Using onQueryStarted:
const sequenceMutex = new Mutex()
async onQueryStarted(id, { dispatch, queryFulfilled }) {
// wait until the sequenceMutex is available
await sequenceMutex.waitForUnlock()
const releaseSequence = await sequenceMutex.acquire()
await queryFulfilled // the endpoint already started here, cant manage the execution
releaseSequence()
}
Using a different baseQuery. But I dont see any way to use a different baseQuery for an especific endpoint:
const sequenceMutex = new Mutex()
export const sequentialBaseQueryWithReauth = async (
args,
api,
extraOptions
) => {
// wait until the sequenceMutex is available
await sequenceMutex.waitForUnlock()
const releaseSequence = await sequenceMutex.acquire()
const result = await baseQueryWithAuthentication(args, api, extraOptions)
releaseSequence()
return result
}
You could iterate on your answer and kick that yourEndpointSequentialBaseQuery.
const apiSlice = api.injectEndpoints({
endpoints: (builder) => ({
yourEndpoint: builder.query({
queryFn: (args, api, extraOptions, baseQuery) => {
await mutex.waitForUnlock()
const releaseSequence = await mutex.acquire()
try {
return baseQuery({
url: '/your-route',
method: 'POST',
body: args,
})
} finally {
releaseSequence()
}
},
}),
}),
})
Finally I created a generic seuqnetial baseQuery function to reuse it across the app.
First, create a baseQuery that accepts a mutex instance as argument:
export const sequentialBaseQueryFactory = (mutex: InstanceType<typeof Mutex>) => {
const sequentialBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
args,
api,
extraOptions,
) => {
await mutex.waitForUnlock()
const releaseSequence = await mutex.acquire()
const result = await yourBaseQuery(args, api, extraOptions) // use your createApi baseQuery
releaseSequence()
return result
}
return sequentialBaseQuery
}
Then, create the custom baseQuery for your endpoint using a mutex instance :
import { Mutex } from 'async-mutex'
// mutex for sequential calls of your endpoint
const yourEndpointSequenceMutex = new Mutex()
const yourEndpointSequentialBaseQuery = sequentialBaseQueryFactory(yourEndpointSequenceMutex)
Use the custom baseQuery in your endoint:
const apiSlice = api.injectEndpoints({
endpoints: (builder) => ({
yourEndpoint: builder.query({
queryFn: (args, api, extraOptions) => {
return yourEndpointSequentialBaseQuery({
url: '/your-route',
method: 'POST',
body: args,
}, api, extraOptions)
},
}),
}),
})

RTK Query get object inside result

my getMovies returns object data but what i need is data.result inside data. How can I directly get data.result?
My API: https://movie-flask.c3-na.altogic.com/movies
My code:
export const movieApi = createApi({
reducerPath: 'movieApi',
baseQuery: fetchBaseQuery({
baseUrl: 'https://movie-flask.c3-na.altogic.com/'
}),
tagTypes: [],
endpoints: (builder) => ({
getMovies: builder.query({
query: () => "movies",
})
}),
})
// Export hooks for usage in functional components
export const { useGetMoviesQuery } = movieApi
I have tried
getMovies: builder.query({
query: () => "movies",
}).result
It does not work.
Below is how i call useGetMoviesQuery
const { data, error, isLoading } = useGetMoviesQuery();
{data && data.result && data.result.map((item, i) => (
<Text key={i}>
{item.name}
</Text>
))}
I want to get rid of data.result and directly want to call it with data.map.
Thanks.
You can use transformResponse:
getMovies: builder.query({
query: () => 'movies',
transformResponse: response => {
return response.result
},
})

Custom queryFn reusing other endpoints

Using code from https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#performing-multiple-requests-with-a-single-query
Note that I added the endpoint getRandomUser:
import {
createApi,
fetchBaseQuery,
FetchBaseQueryError,
} from '#reduxjs/toolkit/query'
import { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUser: builder.query<User, void>({
query: () => ({
url: `users/random`,
}),
}),
getRandomUserPosts: build.query<Post, void>({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random') // avoid repetition
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 }
},
}),
}),
})
Since in my example code I already have a getRandomUser endpoint defined, I would like to avoid repetition in getRandomUserPosts await fetchWithBQ('users/random') and directly call the endpoint getRandomUser.
I tried to access it with _queryApi.endpoints but it doesn't seem to be defined (I don't know if it can point to a key in the very same object endpoints) and even if it would I didn't know how to use it to replace the repetition.
How to approach these situations?

Nexjs + SWR: API resolved without sending a response for /api/projects/<slug>, this may result in stalled requests

Since on first render I was not able to get the router.query I am passing the params from getServerSideProps as follows:
export async function getServerSideProps(context) {
return {
props: { params: context.params },
};
}
Then in the function am trying to do the API call but am getting the API stalled error
API resolved without sending a response for
/api/projects/nichole_robel23, this may result in stalled requests.
This is my code:
export default function Project({ params }) {
const { slug } = params;
let [projectData, setProjectData] = useState([]);
let [loading, setLoading] = useState(true);
const { data } = useSWR('http://localhost:3000/api/projects/' + slug);
useEffect(() => {
if (data) {
setProjectData(data.data.project);
setLoading(false);
}
}, [data]);
......
I have global SWRCofig as follows
<SWRConfig value={{ fetcher: (url) => axios(url).then(r => r.data) }}>
<Layout>
<Component {...pageProps} />
</Layout>
</SWRConfig>
Any way to solve the problem?
You are missing your fetcher–the function that accepts the key of SWR and returns the data, so the API is not being called.
You are also not returning a response correctly from the API–this is most likely a case of not waiting for a promise/async to be fulfilled correctly.
CLIENT
const fetcher = (...args) => fetch(...args).then((res) => res.json());
export default function Home({ params }) {
const { slug } = params;
const [projectData, setProjectData] = useState([]);
const [loading, setLoading] = useState(true);
const { data } = useSWR(`http://localhost:3000/api/projects/${slug}`, fetcher);
useEffect(() => {
if (data) {
setProjectData(data);
setLoading(false);
}
}, [data]);
API
const getData = () => {
return new Promise((resolve, reject) => {
// simulate delay
setTimeout(() => {
return resolve([{ name: 'luke' }, { name: 'darth' }]);
}, 2000);
});
}
export default async (req, res) => {
// below will result in: API resolved without sending a response for /api/projects/vader, this may result in stalled requests
// getData()
// .then((data) => {
// res.status(200).json(data);
// });
// better
const data = await getData();
res.status(200).json(data);
}

PRISMA: How to receive REST API post requests (non GraphQL)?

How to create one route for receiving non graphql post requests?
I have my graphql server, and want to receive some non graphql data on it.
const server = new GraphQLServer({ ... })
server.express.get('/route', async (req, res, done) => {
const params = req.body;
// do some actions with ctx..
})
How can we access to ctx.db.query or ctx.db.mutation from this route?
Thanks!
Related question: https://github.com/prisma/graphql-yoga/issues/482
https://www.prisma.io/forum/t/how-to-create-one-route-for-receiving-rest-api-post-requests/7239
You can use the same variable you passed in the context:
const { prisma } = require('./generated/prisma-client')
const { GraphQLServer } = require('graphql-yoga')
const server = new GraphQLServer({
typeDefs: './schema.graphql',
resolvers,
context: {
prisma,
},
})
server.express.get('/route', async (req, res, done) => {
const params = req.body;
const user = prisma.user({where: {id: params.id} })
res.send(user)
})