Repository: tannerlinsley/react-query
Pull request #1261 (2020-11-05 16:17:22)

feat: add query function context
Closes https://github.com/tannerlinsley/react-query/discussions/1246

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/tannerlinsley/react-query/d2la52f4f](https://vercel.com/tannerlinsley/react-query/d2la52f4f)  
✅ Preview: *In Progress*



Patch:
diff --git a/docs/src/pages/guides/infinite-queries.md b/docs/src/pages/guides/infinite-queries.md
index 9c22aa65c..b1ad51885 100644
--- a/docs/src/pages/guides/infinite-queries.md
+++ b/docs/src/pages/guides/infinite-queries.md
@@ -43,8 +43,8 @@ With this information, we can create a "Load More" UI by:
 import { useInfiniteQuery } from 'react-query'
 
 function Projects() {
-  const fetchProjects = (key, cursor = 0) =>
-    fetch('/api/projects?cursor=' + cursor)
+  const fetchProjects = ({ pageParam = 0 }) =>
+    fetch('/api/projects?cursor=' + pageParam)
 
   const {
     data,
@@ -98,8 +98,8 @@ By default, the variable returned from `getNextPageParam` will be supplied to th
 
 ```js
 function Projects() {
-  const fetchProjects = (key, cursor = 0) =>
-    fetch('/api/projects?cursor=' + cursor)
+  const fetchProjects = ({ pageParam = 0 }) =>
+    fetch('/api/projects?cursor=' + pageParam)
 
   const {
     status,
diff --git a/docs/src/pages/guides/migrating-to-react-query-3.md b/docs/src/pages/guides/migrating-to-react-query-3.md
index 423a48ab7..7890a5971 100644
--- a/docs/src/pages/guides/migrating-to-react-query-3.md
+++ b/docs/src/pages/guides/migrating-to-react-query-3.md
@@ -65,6 +65,32 @@ const queryClient = new QueryClient({
 })
 ```
 
+### Query function parameters
+
+Query functions now get a `QueryFunctionContext` instead of the query key parameters.
+
+The `QueryFunctionContext` contains a `queryKey` and a `pageParam` in case of ininite queries.
+
+useQuery:
+
+```js
+// Old
+useQuery(['post', id], (_key, id) => fetchPost(id))
+
+// New
+useQuery(['post', id], () => fetchPost(id))
+```
+
+useInfiniteQuery:
+
+```js
+// Old
+useInfiniteQuery(['posts'], (_key, pageParam = 0) => fetchPosts(pageParam))
+
+// New
+useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPost(pageParam))
+```
+
 ### usePaginatedQuery()
 
 The `usePaginatedQuery()` hook has been replaced by the `keepPreviousData` option on `useQuery`:
@@ -93,9 +119,13 @@ const {
   fetchNextPage,
   hasNextPage,
   isFetchingNextPage,
-} = useInfiniteQuery('projects', fetchProjects, {
-  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
-})
+} = useInfiniteQuery(
+  'projects',
+  ({ pageParam = 0 }) => fetchProjects(pageParam),
+  {
+    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+  }
+)
 ```
 
 Both directions:
@@ -109,10 +139,14 @@ const {
   hasPreviousPage,
   isFetchingNextPage,
   isFetchingPreviousPage,
-} = useInfiniteQuery('projects', fetchProjects, {
-  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
-  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
-})
+} = useInfiniteQuery(
+  'projects',
+  ({ pageParam = 0 }) => fetchProjects(pageParam),
+  {
+    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+    getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
+  }
+)
 ```
 
 One direction reversed:
@@ -123,13 +157,17 @@ const {
   fetchNextPage,
   hasNextPage,
   isFetchingNextPage,
-} = useInfiniteQuery('projects', fetchProjects, {
-  select: data => ({
-    pages: [...data.pages].reverse(),
-    pageParams: [...data.pageParams].reverse(),
-  }),
-  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
-})
+} = useInfiniteQuery(
+  'projects',
+  ({ pageParam = 0 }) => fetchProjects(pageParam),
+  {
+    select: data => ({
+      pages: [...data.pages].reverse(),
+      pageParams: [...data.pageParams].reverse(),
+    }),
+    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+  }
+)
 ```
 
 Manually removing the first page:
@@ -276,6 +314,12 @@ The `forceFetchOnMount` query option has been replaced by `refetchOnMount: 'alwa
 When `refetchOnMount` was set to `false` any additional components were prevented from refetching on mount.
 In version 3 only the component where the option has been set will not refetch on mount.
 
+### QueryOptions.queryFnParamsFilter
+
+The `queryFnParamsFilter` option has been removed because query functions now get a `QueryFunctionContext` object instead of the query key.
+
+Parameters can still be filtered within the query function itself as the `QueryFunctionContext` also contains the query key.
+
 ### QueryResult.clear()
 
 The `QueryResult.clear()` method has been renamed to `QueryResult.remove()`.
diff --git a/docs/src/pages/guides/paginated-queries.md b/docs/src/pages/guides/paginated-queries.md
index bf2e53c95..abdea69ab 100644
--- a/docs/src/pages/guides/paginated-queries.md
+++ b/docs/src/pages/guides/paginated-queries.md
@@ -27,7 +27,7 @@ Consider the following example where we would ideally want to increment a pageIn
 function Todos() {
   const [page, setPage] = React.useState(0)
 
-  const fetchProjects = (key, page = 0) => fetch('/api/projects?page=' + page)
+  const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page)
 
   const {
     isLoading,
@@ -36,7 +36,7 @@ function Todos() {
     data,
     isFetching,
     isPreviousData,
-  } = useQuery(['projects', page], fetchProjects)
+  } = useQuery(['projects', page], () => fetchProjects(page))
 
   return (
     <div>
diff --git a/docs/src/pages/guides/query-functions.md b/docs/src/pages/guides/query-functions.md
index 34c6c0d9d..f2dfa04f9 100644
--- a/docs/src/pages/guides/query-functions.md
+++ b/docs/src/pages/guides/query-functions.md
@@ -46,7 +46,7 @@ useQuery(['todos', todoId], async () => {
 
 ## Query Function Variables
 
-Query keys are not just for uniquely identifying the data you are fetching, but are also conveniently passed as variables for your query function and while not always necessary, this makes it possible to extract your query functions if needed. The individual parts of the query key get passed through to your query function as parameters in the same order they appear in the array key:
+Query keys are not just for uniquely identifying the data you are fetching, but are also conveniently passed into your query function and while not always necessary, this makes it possible to extract your query functions if needed:
 
 ```js
 function Todos({ completed }) {
@@ -54,23 +54,9 @@ function Todos({ completed }) {
 }
 
 // Access the key, status and page variables in your query function!
-function fetchTodoList(key, { status, page }) {
+function fetchTodoList({ queryKey }) {
+  const { status, page } = queryKey[1]
   return new Promise()
-  // ...
-}
-```
-
-If you send through more items in your query key, they will also be available in your query function:
-
-```js
-function Todo({ todoId, preview }) {
-  const result = useQuery(['todo', todoId, { preview }], fetchTodoById)
-}
-
-// Access status and page in your query function!
-function fetchTodoById(key, todoId, { preview }) {
-  return new Promise()
-  // ...
 }
 ```
 
diff --git a/docs/src/pages/reference/useInfiniteQuery.md b/docs/src/pages/reference/useInfiniteQuery.md
index 178cef810..62ee6ebd1 100644
--- a/docs/src/pages/reference/useInfiniteQuery.md
+++ b/docs/src/pages/reference/useInfiniteQuery.md
@@ -4,9 +4,6 @@ title: useInfiniteQuery
 ---
 
 ```js
-
-const queryFn = (...queryKey, pageParam) // => Promise
-
 const {
   fetchNextPage,
   fetchPreviousPage,
@@ -15,10 +12,10 @@ const {
   isFetchingNextPage,
   isFetchingPreviousPage,
   ...result
-} = useInfiniteQuery(queryKey, queryFn, {
+} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
   ...options,
   getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
-  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor
+  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
 })
 ```
 
@@ -26,6 +23,13 @@ const {
 
 The options for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery) with the addition of the following:
 
+- `queryFn: (context: QueryFunctionContext) => Promise<TData>`
+  - **Required, but only if no default query function has been defined**
+  - The function that the query will use to request data.
+  - Receives a `QueryFunctionContext` object with the following variables:
+    - `queryKey: QueryKey`
+    - `pageParam: unknown | undefined`
+  - Must return a promise that will either resolves data or throws an error.
 - `getNextPageParam: (lastPage, allPages) => unknown | undefined`
   - When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages.
   - It should return a **single variable** that will be passed as the last optional parameter to your query function.
diff --git a/docs/src/pages/reference/useQuery.md b/docs/src/pages/reference/useQuery.md
index 5db3210c9..17539963a 100644
--- a/docs/src/pages/reference/useQuery.md
+++ b/docs/src/pages/reference/useQuery.md
@@ -32,7 +32,6 @@ const {
   onError,
   onSettled,
   onSuccess,
-  queryFnParamsFilter,
   queryKeyHashFn,
   refetchInterval,
   refetchIntervalInBackground,
@@ -63,11 +62,11 @@ const result = useQuery({
   - The query key to use for this query.
   - The query key will be hashed into a stable hash. See [Query Keys](./guides/query-keys) for more information.
   - The query will automatically update when this key changes (as long as `enabled` is not set to `false`).
-- `queryFn: (...params: unknown[]) => Promise<TData>`
+- `queryFn: (context: QueryFunctionContext) => Promise<TData>`
   - **Required, but only if no default query function has been defined**
   - The function that the query will use to request data.
-  - Receives the following variables in the order that they are provided:
-    - Query Key parameters
+  - Receives a `QueryFunctionContext` object with the following variables:
+    - `queryKey: QueryKey`
   - Must return a promise that will either resolves data or throws an error.
 - `enabled: boolean`
   - Set this to `false` to disable this query from automatically running.
@@ -149,10 +148,6 @@ const result = useQuery({
   - Optional
   - Defaults to `false`
   - If set, any previous `data` will be kept when fetching new data because the query key changed.
-- `queryFnParamsFilter: (...params: unknown[]) => unknown[]`
-  - Optional
-  - This function will filter the params that get passed to `queryFn`.
-  - For example, you can filter out the first query key from the params by using `queryFnParamsFilter: params => params.slice(1)`.
 - `structuralSharing: boolean`
   - Optional
   - Defaults to `true`
@@ -174,6 +169,10 @@ const result = useQuery({
   - A derived boolean from the `status` variable above, provided for convenience.
 - `isError: boolean`
   - A derived boolean from the `status` variable above, provided for convenience.
+- `isLoadingError: boolean`
+  - Will be `true` if the query failed while fetching for the first time.
+- `isRefetchError: boolean`
+  - Will be `true` if the query failed while refetching.
 - `data: TData`
   - Defaults to `undefined`.
   - The last successfully resolved data for the query.
diff --git a/examples/basic/src/index.js b/examples/basic/src/index.js
index fce9fac7f..da3d16b33 100644
--- a/examples/basic/src/index.js
+++ b/examples/basic/src/index.js
@@ -90,7 +90,7 @@ function Posts({ setPostId }) {
   );
 }
 
-const getPostById = async (key, id) => {
+const getPostById = async (id) => {
   const { data } = await axios.get(
     `https://jsonplaceholder.typicode.com/posts/${id}`
   );
@@ -98,7 +98,7 @@ const getPostById = async (key, id) => {
 };
 
 function usePost(postId) {
-  return useQuery(["post", postId], getPostById, {
+  return useQuery(["post", postId], () => getPostById(postId), {
     enabled: !!postId,
   });
 }
diff --git a/examples/custom-hooks/src/hooks/usePost.js b/examples/custom-hooks/src/hooks/usePost.js
index a943e5f5c..b6a38c2c9 100644
--- a/examples/custom-hooks/src/hooks/usePost.js
+++ b/examples/custom-hooks/src/hooks/usePost.js
@@ -1,7 +1,7 @@
 import { useQuery } from "react-query";
 import axios from "axios";
 
-const getPostById = async (_, postId) => {
+const getPostById = async (postId) => {
   const { data } = await axios.get(
     `https://jsonplaceholder.typicode.com/posts/${postId}`
   );
@@ -9,5 +9,5 @@ const getPostById = async (_, postId) => {
 };
 
 export default function usePost(postId) {
-  return useQuery(["post", postId], getPostById);
+  return useQuery(["post", postId], () => getPostById(postId));
 }
diff --git a/examples/default-query-function/src/index.js b/examples/default-query-function/src/index.js
index 1e902c89f..7909b77b7 100644
--- a/examples/default-query-function/src/index.js
+++ b/examples/default-query-function/src/index.js
@@ -11,9 +11,9 @@ import {
 import { ReactQueryDevtools } from "react-query-devtools";
 
 // Define a default query function that will receive the query key
-const defaultQueryFn = async (key) => {
+const defaultQueryFn = async ({ queryKey }) => {
   const { data } = await axios.get(
-    `https://jsonplaceholder.typicode.com${key}`
+    `https://jsonplaceholder.typicode.com${queryKey[0]}`
   );
   return data;
 };
diff --git a/examples/load-more-infinite-scroll/pages/index.js b/examples/load-more-infinite-scroll/pages/index.js
index c7126b2b5..745506521 100755
--- a/examples/load-more-infinite-scroll/pages/index.js
+++ b/examples/load-more-infinite-scroll/pages/index.js
@@ -30,8 +30,8 @@ function Example() {
     hasNextPage,
   } = useInfiniteQuery(
     'projects',
-    async (_key, nextId = 0) => {
-      const res = await axios.get('/api/projects?cursor=' + nextId)
+    async ({ pageParam = 0 }) => {
+      const res = await axios.get('/api/projects?cursor=' + pageParam)
       return res.data
     },
     {
diff --git a/examples/nextjs/hooks/usePosts/index.js b/examples/nextjs/hooks/usePosts/index.js
index 7031dadff..8ead3ffbf 100644
--- a/examples/nextjs/hooks/usePosts/index.js
+++ b/examples/nextjs/hooks/usePosts/index.js
@@ -1,16 +1,14 @@
 import ky from 'ky-universal'
 import { useQuery } from 'react-query'
 
-const fetchPosts = async (_, limit) => {
-  const offset = limit ?? 10
-
+const fetchPosts = async (limit = 10) => {
   const parsed = await ky('https://jsonplaceholder.typicode.com/posts').json()
-  const result = parsed.filter(x => x.id <= offset)
+  const result = parsed.filter(x => x.id <= limit)
   return result
 }
 
 const usePosts = limit => {
-  return useQuery(['posts', limit], fetchPosts)
+  return useQuery(['posts', limit], () => fetchPosts(limit))
 }
 
 export { usePosts, fetchPosts }
diff --git a/examples/pagination/pages/index.js b/examples/pagination/pages/index.js
index 788e91b44..d9b1a86c1 100644
--- a/examples/pagination/pages/index.js
+++ b/examples/pagination/pages/index.js
@@ -22,14 +22,14 @@ function Example() {
   const queryClient = useQueryClient()
   const [page, setPage] = React.useState(0)
 
-  const fetchProjects = React.useCallback(async (key, page = 0) => {
+  const fetchProjects = React.useCallback(async (page = 0) => {
     const { data } = await axios.get('/api/projects?page=' + page)
     return data
   }, [])
 
   const { status, data, error, isFetching, isPreviousData } = useQuery(
     ['projects', page],
-    fetchProjects,
+    () => fetchProjects(page),
     { keepPreviousData: true }
   )
 
diff --git a/examples/playground/src/index.js b/examples/playground/src/index.js
index 6b2464d4d..ad4d80446 100644
--- a/examples/playground/src/index.js
+++ b/examples/playground/src/index.js
@@ -181,7 +181,7 @@ function Todos({ initialFilter = "", setEditingIndex }) {
 
   const { status, data, isFetching, error, failureCount, refetch } = useQuery(
     ["todos", { filter }],
-    fetchTodos
+    () => fetchTodos({ filter })
   );
 
   return (
@@ -235,7 +235,7 @@ function EditTodo({ editingIndex, setEditingIndex }) {
   // Don't attempt to query until editingIndex is truthy
   const { status, data, isFetching, error, failureCount, refetch } = useQuery(
     ["todo", { id: editingIndex }],
-    fetchTodoById,
+    () => fetchTodoById({ id: editingIndex }),
     {
       enabled: editingIndex !== null,
     }
@@ -370,7 +370,7 @@ function AddTodo() {
   );
 }
 
-function fetchTodos(key, { filter } = {}) {
+function fetchTodos({ filter } = {}) {
   console.info("fetchTodos", { filter });
   const promise = new Promise((resolve, reject) => {
     setTimeout(() => {
@@ -388,7 +388,7 @@ function fetchTodos(key, { filter } = {}) {
   return promise;
 }
 
-function fetchTodoById(key, { id }) {
+function fetchTodoById({ id }) {
   console.info("fetchTodoById", { id });
   return new Promise((resolve, reject) => {
     setTimeout(() => {
diff --git a/examples/prefetching/pages/[user]/[repo].js b/examples/prefetching/pages/[user]/[repo].js
index c6ec99a39..d22b92d5d 100755
--- a/examples/prefetching/pages/[user]/[repo].js
+++ b/examples/prefetching/pages/[user]/[repo].js
@@ -10,7 +10,7 @@ export default () => {
 
   const { status, data, error, isFetching } = useQuery(
     ['team', id],
-    (key, id) => fetch('/api/data?id=' + id)
+    () => fetch('/api/data?id=' + id)
   )
 
   return (
diff --git a/examples/prefetching/pages/index.js b/examples/prefetching/pages/index.js
index b79e180ae..06425541f 100755
--- a/examples/prefetching/pages/index.js
+++ b/examples/prefetching/pages/index.js
@@ -14,7 +14,7 @@ const getCharacters = async () => {
   return data
 }
 
-const getCharacter = async (key, selectedChar) => {
+const getCharacter = async (selectedChar) => {
   await new Promise(r => setTimeout(r, 500))
   const { data } = await axios.get(
     `https://rickandmortyapi.com/api/character/${selectedChar}`
@@ -41,7 +41,7 @@ function Example() {
 
   const { data: selectedData } = useQuery(
     ['character', selectedChar],
-    getCharacter
+    () => getCharacter(selectedChar)
   )
 
   const prefetchNext = async id => {
diff --git a/src/core/infiniteQueryBehavior.ts b/src/core/infiniteQueryBehavior.ts
index 21bc1d8d9..878ddc83e 100644
--- a/src/core/infiniteQueryBehavior.ts
+++ b/src/core/infiniteQueryBehavior.ts
@@ -1,5 +1,5 @@
 import type { QueryBehavior } from './query'
-import type { InfiniteData, QueryOptions } from './types'
+import type { InfiniteData, QueryFunctionContext, QueryOptions } from './types'
 
 export function infiniteQueryBehavior<
   TData,
@@ -8,7 +8,7 @@ export function infiniteQueryBehavior<
 >(): QueryBehavior<InfiniteData<TData>, TError, TQueryFnData> {
   return {
     onFetch: context => {
-      context.queryFn = () => {
+      context.fetchFn = () => {
         const fetchMore = context.fetchOptions?.meta?.fetchMore
         const pageParam = fetchMore?.pageParam
         const isFetchingNextPage = fetchMore?.direction === 'forward'
@@ -32,8 +32,13 @@ export function infiniteQueryBehavior<
             return Promise.resolve(pages)
           }
 
+          const queryFnContext: QueryFunctionContext = {
+            queryKey: context.queryKey,
+            pageParam: param,
+          }
+
           return Promise.resolve()
-            .then(() => queryFn(...context.params, param))
+            .then(() => queryFn(queryFnContext))
             .then(page => {
               newPageParams = previous
                 ? [param, ...newPageParams]
diff --git a/src/core/query.ts b/src/core/query.ts
index 36574cdff..b434096e1 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -9,10 +9,10 @@ import {
 } from './utils'
 import type {
   InitialDataFunction,
-  QueryFunction,
   QueryKey,
   QueryOptions,
   QueryStatus,
+  QueryFunctionContext,
 } from './types'
 import type { QueryCache } from './queryCache'
 import type { QueryObserver } from './queryObserver'
@@ -47,11 +47,11 @@ export interface QueryState<TData = unknown, TError = unknown> {
 }
 
 export interface FetchContext<TData, TError, TQueryFnData> {
-  state: QueryState<TData, TError>
-  options: QueryOptions<TData, TError, TQueryFnData>
-  params: unknown[]
+  fetchFn: () => unknown | Promise<unknown>
   fetchOptions?: FetchOptions
-  queryFn: QueryFunction
+  options: QueryOptions<TData, TError, TQueryFnData>
+  queryKey: QueryKey
+  state: QueryState<TData, TError>
 }
 
 export interface QueryBehavior<
@@ -338,25 +338,26 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
       }
     }
 
-    // Get the query function params
-    let params = ensureArray(this.queryKey)
-
-    const filter = this.options.queryFnParamsFilter
-    params = filter ? filter(params) : params
+    // Create query function context
+    const queryKey = ensureArray(this.queryKey)
+    const queryFnContext: QueryFunctionContext = {
+      queryKey,
+      pageParam: undefined,
+    }
 
-    // Get query function
-    const queryFn = () =>
+    // Create fetch function
+    const fetchFn = () =>
       this.options.queryFn
-        ? this.options.queryFn(...params)
+        ? this.options.queryFn(queryFnContext)
         : Promise.reject('Missing queryFn')
 
     // Trigger behavior hook
     const context: FetchContext<TData, TError, TQueryFnData> = {
       fetchOptions,
       options: this.options,
-      params,
+      queryKey,
       state: this.state,
-      queryFn,
+      fetchFn,
     }
 
     if (this.options.behavior?.onFetch) {
@@ -373,7 +374,7 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
 
     // Try to fetch the data
     this.retryer = new Retryer({
-      fn: context.queryFn,
+      fn: context.fetchFn,
       onFail: () => {
         this.dispatch({ type: 'failed' })
       },
diff --git a/src/core/queryClient.ts b/src/core/queryClient.ts
index 83d7872fb..9bbf6de77 100644
--- a/src/core/queryClient.ts
+++ b/src/core/queryClient.ts
@@ -317,7 +317,7 @@ export class QueryClient {
 
   getQueryDefaults(
     queryKey?: QueryKey
-  ): QueryObserverOptions<any, any, any> | undefined {
+  ): QueryObserverOptions<any, any, any, any> | undefined {
     return queryKey
       ? this.queryDefaults.find(x => partialMatchKey(queryKey, x.queryKey))
           ?.defaultOptions
@@ -348,7 +348,7 @@ export class QueryClient {
       : undefined
   }
 
-  defaultQueryOptions<T extends QueryOptions<any, any>>(options?: T): T {
+  defaultQueryOptions<T extends QueryOptions<any, any, any>>(options?: T): T {
     if (options?._defaulted) {
       return options
     }
@@ -360,9 +360,9 @@ export class QueryClient {
     } as T
   }
 
-  defaultQueryObserverOptions<T extends QueryObserverOptions<any, any>>(
-    options?: T
-  ): T {
+  defaultQueryObserverOptions<
+    T extends QueryObserverOptions<any, any, any, any>
+  >(options?: T): T {
     return this.defaultQueryOptions(options)
   }
 
diff --git a/src/core/types.ts b/src/core/types.ts
index d5747a649..0b8228190 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -5,7 +5,14 @@ import type { QueryFilters } from './utils'
 
 export type QueryKey = string | unknown[]
 
-export type QueryFunction<T = unknown> = (...args: any[]) => T | Promise<T>
+export type QueryFunction<T = unknown> = (
+  context: QueryFunctionContext
+) => T | Promise<T>
+
+export interface QueryFunctionContext<TPageParam = any> {
+  queryKey: QueryKey
+  pageParam?: TPageParam
+}
 
 export type InitialDataFunction<T> = () => T | undefined
 
@@ -18,12 +25,12 @@ export type QueryKeyHashFunction = (queryKey: QueryKey) => string
 export type GetPreviousPageParamFunction<TQueryFnData = unknown> = (
   firstPage: TQueryFnData,
   allPages: TQueryFnData[]
-) => unknown | undefined
+) => unknown
 
 export type GetNextPageParamFunction<TQueryFnData = unknown> = (
   lastPage: TQueryFnData,
   allPages: TQueryFnData[]
-) => unknown | undefined
+) => unknown
 
 export interface InfiniteData<TData> {
   pages: TData[]
@@ -49,7 +56,6 @@ export interface QueryOptions<
   queryHash?: string
   queryKey?: QueryKey
   queryKeyHashFn?: QueryKeyHashFunction
-  queryFnParamsFilter?: (args: unknown[]) => unknown[]
   initialData?: TData | InitialDataFunction<TData>
   behavior?: QueryBehavior<TData, TError, TQueryFnData>
   /**
diff --git a/src/core/utils.ts b/src/core/utils.ts
index 4c7dad649..dffaf2655 100644
--- a/src/core/utils.ts
+++ b/src/core/utils.ts
@@ -88,7 +88,7 @@ export function timeUntilStale(updatedAt: number, staleTime?: number): number {
   return Math.max(updatedAt + (staleTime || 0) - Date.now(), 0)
 }
 
-export function parseQueryArgs<TOptions extends QueryOptions<any, any>>(
+export function parseQueryArgs<TOptions extends QueryOptions<any, any, any>>(
   arg1: QueryKey | TOptions,
   arg2?: QueryFunction<any> | TOptions,
   arg3?: TOptions


Test patch:
diff --git a/src/hydration/tests/react.test.tsx b/src/hydration/tests/react.test.tsx
index 47871e344..70a371fa3 100644
--- a/src/hydration/tests/react.test.tsx
+++ b/src/hydration/tests/react.test.tsx
@@ -14,7 +14,7 @@ describe('React hydration', () => {
   beforeAll(async () => {
     const queryCache = new QueryCache()
     const queryClient = new QueryClient({ queryCache })
-    await queryClient.prefetchQuery('string', dataQuery)
+    await queryClient.prefetchQuery('string', () => dataQuery('string'))
     const dehydrated = dehydrate(queryClient)
     stringifiedState = JSON.stringify(dehydrated)
     queryClient.clear()
@@ -28,7 +28,7 @@ describe('React hydration', () => {
 
       function Page() {
         useHydrate(dehydratedState)
-        const { data } = useQuery('string', dataQuery)
+        const { data } = useQuery('string', () => dataQuery('string'))
         return (
           <div>
             <h1>{data}</h1>
@@ -55,7 +55,7 @@ describe('React hydration', () => {
       const queryClient = new QueryClient({ queryCache })
 
       function Page({ queryKey }: { queryKey: string }) {
-        const { data } = useQuery(queryKey, dataQuery)
+        const { data } = useQuery(queryKey, () => dataQuery(queryKey))
         return (
           <div>
             <h1>{data}</h1>
@@ -81,7 +81,9 @@ describe('React hydration', () => {
       await intermediateClient.prefetchQuery('string', () =>
         dataQuery('should change')
       )
-      await intermediateClient.prefetchQuery('added string', dataQuery)
+      await intermediateClient.prefetchQuery('added string', () =>
+        dataQuery('added string')
+      )
       const dehydrated = dehydrate(intermediateClient)
       intermediateClient.clear()
 
@@ -110,7 +112,7 @@ describe('React hydration', () => {
       const queryClient = new QueryClient({ queryCache })
 
       function Page() {
-        const { data } = useQuery('string', dataQuery)
+        const { data } = useQuery('string', () => dataQuery('string'))
         return (
           <div>
             <h1>{data}</h1>
diff --git a/src/react/tests/ssr.test.tsx b/src/react/tests/ssr.test.tsx
index e5e57201d..0fcc19af1 100644
--- a/src/react/tests/ssr.test.tsx
+++ b/src/react/tests/ssr.test.tsx
@@ -89,13 +89,9 @@ describe('Server Side Rendering', () => {
 
     function Page() {
       const [page, setPage] = React.useState(1)
-      const { data } = useQuery(
-        [key, page],
-        async (_: string, pageArg: number) => {
-          return pageArg
-        },
-        { initialData: 1 }
-      )
+      const { data } = useQuery([key, page], async () => page, {
+        initialData: 1,
+      })
 
       return (
         <div>
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index 0e5308280..025600ff1 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -57,7 +57,7 @@ describe('useInfiniteQuery', () => {
     function Page() {
       const state = useInfiniteQuery(
         key,
-        (_key: string, pageParam: number = 0) => pageParam,
+        ({ pageParam = 0 }) => Number(pageParam),
         {
           getNextPageParam: lastPage => lastPage + 1,
         }
@@ -137,11 +137,11 @@ describe('useInfiniteQuery', () => {
       const start = 1
       const state = useInfiniteQuery(
         key,
-        async (_key, pageParam: number = start) => {
+        async ({ pageParam = start }) => {
           if (pageParam === 2) {
             throw new Error('error')
           }
-          return pageParam
+          return Number(pageParam)
         },
         {
           retry: 1,
@@ -180,9 +180,9 @@ describe('useInfiniteQuery', () => {
 
       const state = useInfiniteQuery(
         [key, order],
-        async (_key, orderParam, pageParam = 0) => {
+        async ({ pageParam = 0 }) => {
           await sleep(10)
-          return `${pageParam}-${orderParam}`
+          return `${pageParam}-${order}`
         },
         {
           getNextPageParam: () => 1,
@@ -300,7 +300,7 @@ describe('useInfiniteQuery', () => {
     function Page() {
       const state = useInfiniteQuery(
         key,
-        (_key, pageParam: number = 0) => pageParam,
+        ({ pageParam = 0 }) => Number(pageParam),
         {
           select: data => ({
             pages: [...data.pages].reverse(),
@@ -353,9 +353,9 @@ describe('useInfiniteQuery', () => {
       const start = 10
       const state = useInfiniteQuery(
         key,
-        async (_key, pageParam: number = start) => {
+        async ({ pageParam = start }) => {
           await sleep(10)
-          return pageParam
+          return Number(pageParam)
         },
         {
           getPreviousPageParam: firstPage => firstPage - 1,
@@ -423,9 +423,8 @@ describe('useInfiniteQuery', () => {
     const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const state = useInfiniteQuery(
-        key,
-        (_key, pageParam: number = 10) => pageParam
+      const state = useInfiniteQuery(key, ({ pageParam = 10 }) =>
+        Number(pageParam)
       )
 
       states.push(state)
@@ -513,7 +512,7 @@ describe('useInfiniteQuery', () => {
     function Page() {
       const state = useInfiniteQuery(
         key,
-        (_key, pageParam: number = 10) => pageParam,
+        ({ pageParam = 10 }) => Number(pageParam),
         {
           getPreviousPageParam: firstPage => firstPage - 1,
           getNextPageParam: lastPage => lastPage + 1,
@@ -606,9 +605,9 @@ describe('useInfiniteQuery', () => {
       const start = 10
       const state = useInfiniteQuery(
         key,
-        async (_key, pageParam: number = start) => {
+        async ({ pageParam = start }) => {
           await sleep(50)
-          return pageParam
+          return Number(pageParam)
         },
         {
           getNextPageParam: lastPage => lastPage + 1,
@@ -681,9 +680,9 @@ describe('useInfiniteQuery', () => {
       const start = 10
       const state = useInfiniteQuery(
         key,
-        async (_key, pageParam: number = start) => {
+        async ({ pageParam = start }) => {
           await sleep(50)
-          return pageParam
+          return Number(pageParam)
         },
         {
           getNextPageParam: lastPage => lastPage + 1,
@@ -731,9 +730,9 @@ describe('useInfiniteQuery', () => {
     function Page() {
       const state = useInfiniteQuery(
         key,
-        async (_key, pageParam: number = 0) => {
+        async ({ pageParam = 0 }) => {
           await sleep(10)
-          return pageParam
+          return Number(pageParam)
         },
         {
           getNextPageParam: lastPage => lastPage + 1,
@@ -797,9 +796,9 @@ describe('useInfiniteQuery', () => {
 
       const state = useInfiniteQuery(
         key,
-        async (_key, pageParam: number = firstPage) => {
+        async ({ pageParam = firstPage }) => {
           await sleep(10)
-          return pageParam
+          return Number(pageParam)
         },
         {
           getNextPageParam: lastPage => lastPage + 1,
@@ -883,9 +882,13 @@ describe('useInfiniteQuery', () => {
     const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const state = useInfiniteQuery(key, (_key, pageParam = 1) => pageParam, {
-        getNextPageParam: () => undefined,
-      })
+      const state = useInfiniteQuery(
+        key,
+        ({ pageParam = 1 }) => Number(pageParam),
+        {
+          getNextPageParam: () => undefined,
+        }
+      )
 
       states.push(state)
 
@@ -918,7 +921,7 @@ describe('useInfiniteQuery', () => {
     const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const state = useInfiniteQuery(key, (_key, pageParam = 10) => pageParam, {
+      const state = useInfiniteQuery(key, ({ pageParam = 10 }) => pageParam, {
         initialData: { pages: [10], pageParams: [undefined] },
         getNextPageParam: lastPage => (lastPage === 10 ? 11 : undefined),
       })
@@ -954,7 +957,7 @@ describe('useInfiniteQuery', () => {
     const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const state = useInfiniteQuery(key, (_key, pageParam = 10) => pageParam, {
+      const state = useInfiniteQuery(key, ({ pageParam = 10 }) => pageParam, {
         initialData: { pages: [10], pageParams: [undefined] },
         getNextPageParam: () => undefined,
       })
@@ -1014,8 +1017,8 @@ describe('useInfiniteQuery', () => {
         refetch,
       } = useInfiniteQuery<Result, Error>(
         key,
-        (_key, nextId = 0) =>
-          fetchItemsWithLimit(nextId, fetchCountRef.current++),
+        ({ pageParam = 0 }) =>
+          fetchItemsWithLimit(pageParam, fetchCountRef.current++),
         {
           getNextPageParam: lastPage => lastPage.nextId,
         }
@@ -1141,11 +1144,11 @@ describe('useInfiniteQuery', () => {
         refetch,
       } = useInfiniteQuery<Result, Error>(
         key,
-        (_key, nextId = 0) =>
+        ({ pageParam = 0 }) =>
           fetchItems(
-            nextId,
+            pageParam,
             fetchCountRef.current++,
-            nextId === MAX || (nextId === MAX - 1 && isRemovedLastPage)
+            pageParam === MAX || (pageParam === MAX - 1 && isRemovedLastPage)
           ),
         {
           getNextPageParam: lastPage => lastPage.nextId,
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index c7e725160..d59287860 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -49,32 +49,6 @@ describe('useQuery', () => {
       expectType<string | undefined>(withError.data)
       expectType<Error | null>(withError.error)
 
-      // it should be possible to specify the query function arguments
-      useQuery<void, string, [string]>(key, arg1 => {
-        expectType<string>(arg1)
-      })
-
-      // the query function arguments should default to unknown
-      useQuery(key, arg1 => {
-        expectType<unknown>(arg1)
-      })
-
-      // the query function arguments should default to any if other generics are provided
-      useQuery<void>(key, arg1 => {
-        expectType<any>(arg1)
-        arg1.someMethod()
-      })
-
-      // it should be possible to specify the query function argument types
-      useQuery(key, (arg1: string) => {
-        expectType<string>(arg1)
-      })
-
-      // it should be possible to specify the query function argument types when generics are provided
-      useQuery<void>(key, (arg1: string) => {
-        expectType<string>(arg1)
-      })
-
       // it should provide the result type in the configuration
       useQuery([key], async () => true, {
         onSuccess: data => expectType<boolean>(data),
@@ -1700,7 +1674,7 @@ describe('useQuery', () => {
 
     renderWithClient(queryClient, <Page />)
 
-    expect(queryFn).toHaveBeenCalledWith(key, variables)
+    expect(queryFn).toHaveBeenCalledWith({ queryKey: [key, variables] })
   })
 
   it('should not refetch query on focus when `enabled` is set to `false`', async () => {
@@ -2658,7 +2632,7 @@ describe('useQuery', () => {
 
   it('should accept an empty string as query key', async () => {
     function Page() {
-      const result = useQuery('', (key: string) => key)
+      const result = useQuery('', ctx => ctx.queryKey)
       return <>{JSON.stringify(result.data)}</>
     }
 
@@ -2669,13 +2643,13 @@ describe('useQuery', () => {
 
   it('should accept an object as query key', async () => {
     function Page() {
-      const result = useQuery([{ a: 'a' }], (key: { a: string }) => key)
+      const result = useQuery([{ a: 'a' }], ctx => ctx.queryKey)
       return <>{JSON.stringify(result.data)}</>
     }
 
     const rendered = renderWithClient(queryClient, <Page />)
 
-    await waitFor(() => rendered.getByText('{"a":"a"}'))
+    await waitFor(() => rendered.getByText('[{"a":"a"}]'))
   })
 
   it('should refetch if any query instance becomes enabled', async () => {