@@ -111,8 +111,13 @@ export type QueryLifecycleQueryExtraOptions<
111111 * ```
112112 */
113113 onQueryStarted ?(
114- arg : QueryArg ,
115- api : QueryLifecycleApi < QueryArg , BaseQuery , ResultType , ReducerPath > ,
114+ queryArgument : QueryArg ,
115+ queryLifeCycleApi : QueryLifecycleApi <
116+ QueryArg ,
117+ BaseQuery ,
118+ ResultType ,
119+ ReducerPath
120+ > ,
116121 ) : Promise < void > | void
117122}
118123
@@ -171,8 +176,13 @@ export type QueryLifecycleMutationExtraOptions<
171176 * ```
172177 */
173178 onQueryStarted ?(
174- arg : QueryArg ,
175- api : MutationLifecycleApi < QueryArg , BaseQuery , ResultType , ReducerPath > ,
179+ queryArgument : QueryArg ,
180+ mutationLifeCycleApi : MutationLifecycleApi <
181+ QueryArg ,
182+ BaseQuery ,
183+ ResultType ,
184+ ReducerPath
185+ > ,
176186 ) : Promise < void > | void
177187}
178188
@@ -192,6 +202,212 @@ export type MutationLifecycleApi<
192202> = MutationBaseLifecycleApi < QueryArg , BaseQuery , ResultType , ReducerPath > &
193203 QueryLifecyclePromises < ResultType , BaseQuery >
194204
205+ /**
206+ * Provides a way to define a strongly-typed version of
207+ * {@linkcode QueryLifecycleQueryExtraOptions.onQueryStarted | onQueryStarted}
208+ * for a specific query.
209+ *
210+ * @example
211+ * <caption>#### __Create and reuse a strongly-typed `onQueryStarted` function__</caption>
212+ *
213+ * ```ts
214+ * import type { TypedQueryOnQueryStarted } from '@reduxjs/toolkit/query'
215+ * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
216+ *
217+ * type Post = {
218+ * id: number
219+ * title: string
220+ * userId: number
221+ * }
222+ *
223+ * type PostsApiResponse = {
224+ * posts: Post[]
225+ * total: number
226+ * skip: number
227+ * limit: number
228+ * }
229+ *
230+ * type QueryArgument = number | undefined
231+ *
232+ * type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
233+ *
234+ * const baseApiSlice = createApi({
235+ * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
236+ * reducerPath: 'postsApi',
237+ * tagTypes: ['Posts'],
238+ * endpoints: (builder) => ({
239+ * getPosts: builder.query<PostsApiResponse, void>({
240+ * query: () => `/posts`,
241+ * }),
242+ *
243+ * getPostById: builder.query<Post, QueryArgument>({
244+ * query: (postId) => `/posts/${postId}`,
245+ * }),
246+ * }),
247+ * })
248+ *
249+ * const updatePostOnFulfilled: TypedQueryOnQueryStarted<
250+ * PostsApiResponse,
251+ * QueryArgument,
252+ * BaseQueryFunction,
253+ * 'postsApi'
254+ * > = async (queryArgument, { dispatch, queryFulfilled }) => {
255+ * const result = await queryFulfilled
256+ *
257+ * const { posts } = result.data
258+ *
259+ * // Pre-fill the individual post entries with the results
260+ * // from the list endpoint query
261+ * dispatch(
262+ * baseApiSlice.util.upsertQueryEntries(
263+ * posts.map((post) => ({
264+ * endpointName: 'getPostById',
265+ * arg: post.id,
266+ * value: post,
267+ * })),
268+ * ),
269+ * )
270+ * }
271+ *
272+ * export const extendedApiSlice = baseApiSlice.injectEndpoints({
273+ * endpoints: (builder) => ({
274+ * getPostsByUserId: builder.query<PostsApiResponse, QueryArgument>({
275+ * query: (userId) => `/posts/user/${userId}`,
276+ *
277+ * onQueryStarted: updatePostOnFulfilled,
278+ * }),
279+ * }),
280+ * })
281+ * ```
282+ *
283+ * @template ResultType - The type of the result `data` returned by the query.
284+ * @template QueryArgumentType - The type of the argument passed into the query.
285+ * @template BaseQueryFunctionType - The type of the base query function being used.
286+ * @template ReducerPath - The type representing the `reducerPath` for the API slice.
287+ *
288+ * @since 2.4.0
289+ * @public
290+ */
291+ export type TypedQueryOnQueryStarted <
292+ ResultType ,
293+ QueryArgumentType ,
294+ BaseQueryFunctionType extends BaseQueryFn ,
295+ ReducerPath extends string = string ,
296+ > = QueryLifecycleQueryExtraOptions <
297+ ResultType ,
298+ QueryArgumentType ,
299+ BaseQueryFunctionType ,
300+ ReducerPath
301+ > [ 'onQueryStarted' ]
302+
303+ /**
304+ * Provides a way to define a strongly-typed version of
305+ * {@linkcode QueryLifecycleMutationExtraOptions.onQueryStarted | onQueryStarted}
306+ * for a specific mutation.
307+ *
308+ * @example
309+ * <caption>#### __Create and reuse a strongly-typed `onQueryStarted` function__</caption>
310+ *
311+ * ```ts
312+ * import type { TypedMutationOnQueryStarted } from '@reduxjs/toolkit/query'
313+ * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
314+ *
315+ * type Post = {
316+ * id: number
317+ * title: string
318+ * userId: number
319+ * }
320+ *
321+ * type PostsApiResponse = {
322+ * posts: Post[]
323+ * total: number
324+ * skip: number
325+ * limit: number
326+ * }
327+ *
328+ * type QueryArgument = Pick<Post, 'id'> & Partial<Post>
329+ *
330+ * type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
331+ *
332+ * const baseApiSlice = createApi({
333+ * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
334+ * reducerPath: 'postsApi',
335+ * tagTypes: ['Posts'],
336+ * endpoints: (builder) => ({
337+ * getPosts: builder.query<PostsApiResponse, void>({
338+ * query: () => `/posts`,
339+ * }),
340+ *
341+ * getPostById: builder.query<Post, number>({
342+ * query: (postId) => `/posts/${postId}`,
343+ * }),
344+ * }),
345+ * })
346+ *
347+ * const updatePostOnFulfilled: TypedMutationOnQueryStarted<
348+ * Post,
349+ * QueryArgument,
350+ * BaseQueryFunction,
351+ * 'postsApi'
352+ * > = async ({ id, ...patch }, { dispatch, queryFulfilled }) => {
353+ * const patchCollection = dispatch(
354+ * baseApiSlice.util.updateQueryData('getPostById', id, (draftPost) => {
355+ * Object.assign(draftPost, patch)
356+ * }),
357+ * )
358+ *
359+ * try {
360+ * await queryFulfilled
361+ * } catch {
362+ * patchCollection.undo()
363+ * }
364+ * }
365+ *
366+ * export const extendedApiSlice = baseApiSlice.injectEndpoints({
367+ * endpoints: (builder) => ({
368+ * addPost: builder.mutation<Post, Omit<QueryArgument, 'id'>>({
369+ * query: (body) => ({
370+ * url: `posts/add`,
371+ * method: 'POST',
372+ * body,
373+ * }),
374+ *
375+ * onQueryStarted: updatePostOnFulfilled,
376+ * }),
377+ *
378+ * updatePost: builder.mutation<Post, QueryArgument>({
379+ * query: ({ id, ...patch }) => ({
380+ * url: `post/${id}`,
381+ * method: 'PATCH',
382+ * body: patch,
383+ * }),
384+ *
385+ * onQueryStarted: updatePostOnFulfilled,
386+ * }),
387+ * }),
388+ * })
389+ * ```
390+ *
391+ * @template ResultType - The type of the result `data` returned by the query.
392+ * @template QueryArgumentType - The type of the argument passed into the query.
393+ * @template BaseQueryFunctionType - The type of the base query function being used.
394+ * @template ReducerPath - The type representing the `reducerPath` for the API slice.
395+ *
396+ * @since 2.4.0
397+ * @public
398+ */
399+ export type TypedMutationOnQueryStarted <
400+ ResultType ,
401+ QueryArgumentType ,
402+ BaseQueryFunctionType extends BaseQueryFn ,
403+ ReducerPath extends string = string ,
404+ > = QueryLifecycleMutationExtraOptions <
405+ ResultType ,
406+ QueryArgumentType ,
407+ BaseQueryFunctionType ,
408+ ReducerPath
409+ > [ 'onQueryStarted' ]
410+
195411export const buildQueryLifecycleHandler : InternalHandlerBuilder = ( {
196412 api,
197413 context,
0 commit comments