Repository: tannerlinsley/react-query
Pull request #1135 (2020-10-04 19:10:36)

feat: add bi-directional infinite query support
PR for discussion:

Adds ability to automatically support both directions:
```js
const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage
} = useInfiniteQuery(key, fn, {
  getNextPageParam: lastPage => lastPage.nextId,
  getPreviousPageParam: firstPage => firstPage.prevId
})

return (
  <>
    <button onClick={fetchPreviousPage} disabled={!hasPreviousPage || isFetchingPreviousPage}>Load previous</button>
     ...
    <button onClick={fetchNextPage} disabled={!hasNextPage || isFetchingNextPage}>Load next</button>
  </>
)
```

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/fnzyb21k8](https://vercel.com/tannerlinsley/react-query/fnzyb21k8)  
✅ Preview: [https://react-query-git-feature-bi-directional-infinite.tannerlinsley.vercel.app](https://react-query-git-feature-bi-directional-infinite.tannerlinsley.vercel.app)


tannerlinsley: I like it. Though, personally the append/prepend option is strange to think about. Technically this means that you can fetchNextPage(), but it would get prepended, correct? (and visa versa)

I realize that might be helpful for like... a news feed concept. But wouldn't that just end up being a visual reversal at render time and not at the data model?
boschni: Yeah the append mode is indeed for those kind of scenarios, where new pages appear at the top instead of at the bottom. Reversing the list when rendering would have the same effect. Mostly added it for convenience and because the reporter of https://github.com/tannerlinsley/react-query/issues/1086 was trying to load previous pages instead of reversing the list. Don’t have a strong opinion on this option though
tannerlinsley: The amount of code to support the option is tiny and it also won’t carry a lot of opinion with it into the future. So I think it’s fine. 
boschni: This one should be ready. Also includes support for refetching when manual page params are provided
tannerlinsley: Excellent usage of the select option to avoid the reversal option. 

Patch:
diff --git a/docs/src/pages/guides/infinite-queries.md b/docs/src/pages/guides/infinite-queries.md
index 15002bea4..a341341c8 100644
--- a/docs/src/pages/guides/infinite-queries.md
+++ b/docs/src/pages/guides/infinite-queries.md
@@ -8,10 +8,11 @@ Rendering lists that can additively "load more" data onto an existing set of dat
 When using `useInfiniteQuery`, you'll notice a few things are different:
 
 - `data` is now an array of arrays that contain query group results, instead of the query results themselves
-- A `fetchMore` function is now available
-- A `getFetchMore` option is available for both determining if there is more data to load and the information to fetch it. This information is supplied as an additional parameter in the query function (which can optionally be overridden when calling the `fetchMore` function)
-- A `canFetchMore` boolean is now available and is `true` if `getFetchMore` returns a truthy value
-- An `isFetchingMore` boolean is now available to distinguish between a background refresh state and a loading more state
+- The `fetchNextPage` and `fetchPreviousPage` functions are now available
+- The `getNextPageParam` and `getPreviousPageParam` options are available for both determining if there is more data to load and the information to fetch it. This information is supplied as an additional parameter in the query function (which can optionally be overridden when calling the `fetchNextPage` or `fetchPreviousPage` functions)
+- A `hasNextPage` boolean is now available and is `true` if `getNextPageParam` returns a value other than `undefined`.
+- A `hasPreviousPage` boolean is now available and is `true` if `getPreviousPageParam` returns a value other than `undefined`.
+- The `isFetchingNextPage` and `isFetchingPreviousPage` booleans are now available to distinguish between a background refresh state and a loading more state
 
 ## Example
 
@@ -31,10 +32,10 @@ fetch('/api/projects?cursor=9')
 With this information, we can create a "Load More" UI by:
 
 - Waiting for `useInfiniteQuery` to request the first group of data by default
-- Returning the information for the next query in `getFetchMore`
-- Calling `fetchMore` function
+- Returning the information for the next query in `getNextPageParam`
+- Calling `fetchNextPage` function
 
-> Note: It's very important you do not call `fetchMore` with arguments unless you want them to override the `fetchMoreInfo` data returned from the `getFetchMore` function. eg. Do not do this: `<button onClick={fetchMore} />` as this would send the onClick event to the `fetchMore` function.
+> Note: It's very important you do not call `fetchNextPage` with arguments unless you want them to override the `pageParam` data returned from the `getNextPageParam` function. eg. Do not do this: `<button onClick={fetchNextPage} />` as this would send the onClick event to the `fetchNextPage` function.
 
 ```js
 import { useInfiniteQuery } from 'react-query'
@@ -44,14 +45,14 @@ function Projects() {
     fetch('/api/projects?cursor=' + cursor)
 
   const {
-    status,
     data,
+    fetchNextPage,
+    hasNextPage,
     isFetching,
-    isFetchingMore,
-    fetchMore,
-    canFetchMore,
+    isFetchingNextPage,
+    status,
   } = useInfiniteQuery('projects', fetchProjects, {
-    getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
+    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
   })
 
   return status === 'loading' ? (
@@ -69,17 +70,17 @@ function Projects() {
       ))}
       <div>
         <button
-          onClick={() => fetchMore()}
-          disabled={!canFetchMore || isFetchingMore}
+          onClick={() => fetchNextPage()}
+          disabled={!hasNextPage || isFetchingNextPage}
         >
-          {isFetchingMore
+          {isFetchingNextPage
             ? 'Loading more...'
-            : canFetchMore
+            : hasNextPage
             ? 'Load More'
             : 'Nothing more to load'}
         </button>
       </div>
-      <div>{isFetching && !isFetchingMore ? 'Fetching...' : null}</div>
+      <div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
     </>
   )
 }
@@ -91,7 +92,7 @@ When an infinite query becomes `stale` and needs to be refetched, each group is
 
 ## What if I need to pass custom information to my query function?
 
-By default, the info returned from `getFetchMore` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchMore` function which will override the default info like so:
+By default, the variable returned from `getNextPageParam` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchNextPage` function which will override the default variable like so:
 
 ```js
 function Projects() {
@@ -102,24 +103,35 @@ function Projects() {
     status,
     data,
     isFetching,
-    isFetchingMore,
-    fetchMore,
-    canFetchMore,
+    isFetchingNextPage,
+    fetchNextPage,
+    hasNextPage,
   } = useInfiniteQuery('projects', fetchProjects, {
-    getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
+    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
   })
 
-  // Pass your own custom fetchMoreInfo
-  const skipToCursor50 = () => fetchMore(50)
+  // Pass your own page param
+  const skipToCursor50 = () => fetchNextPage({ pageParam: 50 })
 }
 ```
 
-## What if I want to infinitely load more data in reverse?
+## What if I want to implement a bi-directional infinite list?
 
-Sometimes you may not want to **append** infinitely loaded data, but instead **prepend** it. If this is case, you can use `fetchMore`'s `previous` option, eg.
+Bi-directional lists can be implemented by using the `getPreviousPageParam`, `fetchPreviousPage`, `hasPreviousPage` and `isFetchingPreviousPage` properties and functions.
 
 ```js
-fetchMore(previousPageVariables, { previous: true })
+useInfiniteQuery('projects', fetchProjects, {
+  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
+})
 ```
 
-This will ensure the new data is prepended to the data array instead of appended.
+## What if I want to show the pages in reversed order?
+
+Sometimes you may want to show the pages in reversed order. If this is case, you can use the `select` option:
+
+```js
+useInfiniteQuery('projects', fetchProjects, {
+  select: pages => [...pages].reverse(),
+})
+```
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 4890b6d89..69d4a0c00 100644
--- a/docs/src/pages/guides/migrating-to-react-query-3.md
+++ b/docs/src/pages/guides/migrating-to-react-query-3.md
@@ -81,6 +81,98 @@ function Page({ page }) {
 }
 ```
 
+### useInfiniteQuery()
+
+The `useInfiniteQuery()` interface has changed to fully support bi-directional infinite lists.
+
+One direction:
+
+```js
+const {
+  data,
+  fetchNextPage,
+  hasNextPage,
+  isFetchingNextPage,
+} = useInfiniteQuery('projects', fetchProjects, {
+  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+})
+```
+
+Both directions:
+
+```js
+const {
+  data,
+  fetchNextPage,
+  fetchPreviousPage,
+  hasNextPage,
+  hasPreviousPage,
+  isFetchingNextPage,
+  isFetchingPreviousPage,
+} = useInfiniteQuery('projects', fetchProjects, {
+  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
+})
+```
+
+One direction reversed:
+
+```js
+const {
+  data,
+  fetchNextPage,
+  hasNextPage,
+  isFetchingNextPage,
+} = useInfiniteQuery('projects', fetchProjects, {
+  select: pages => [...pages].reverse(),
+  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
+})
+```
+
+### useMutation()
+
+The `useMutation()` hook now returns an object instead of an array:
+
+```js
+// Old:
+const [mutate, { status, reset }] = useMutation()
+
+// New:
+const { mutate, status, reset } = useMutation()
+```
+
+Previously the `mutate` function returned a promise which resolved to `undefined` if a mutation failed instead of throwing.
+We got a lot of questions regarding this behavior as users expected the promise to behave like a regular promise.
+Because of this the `mutate` function is now split into a `mutate` and `mutateAsync` function.
+
+The `mutate` function can be used when using callbacks:
+
+```js
+const { mutate } = useMutation(addTodo)
+
+mutate('todo', {
+  onSuccess: data => {
+    console.log(data)
+  },
+  onError: error => {
+    console.error(error)
+  },
+})
+```
+
+The `mutateAsync` function can be used when using async/await:
+
+```js
+const { mutateAsync } = useMutation(addTodo)
+
+try {
+  const data = await mutateAsync('todo')
+  console.log(data)
+} catch (error) {
+  console.error(error)
+}
+```
+
 ### Query object syntax
 
 The object syntax has been collapsed:
diff --git a/docs/src/pages/reference/useInfiniteQuery.md b/docs/src/pages/reference/useInfiniteQuery.md
index 5d0941bf1..f257c0cb0 100644
--- a/docs/src/pages/reference/useInfiniteQuery.md
+++ b/docs/src/pages/reference/useInfiniteQuery.md
@@ -5,16 +5,20 @@ title: useInfiniteQuery
 
 ```js
 
-const queryFn = (...queryKey, fetchMoreVariable) // => Promise
+const queryFn = (...queryKey, pageParam) // => Promise
 
 const {
-  isFetchingMore,
-  fetchMore,
-  canFetchMore,
+  fetchNextPage,
+  fetchPreviousPage,
+  hasNextPage,
+  hasPreviousPage,
+  isFetchingNextPage,
+  isFetchingPreviousPage,
   ...result
 } = useInfiniteQuery(queryKey, queryFn, {
   ...options,
-  getFetchMore: (lastPage, allPages) => fetchMoreVariable
+  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
+  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor
 })
 ```
 
@@ -22,18 +26,30 @@ const {
 
 The options for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery) with the addition of the following:
 
-- `getFetchMore: (lastPage, allPages) => fetchMoreVariable | boolean`
+- `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
+  - It should return a **single variable** that will be passed as the last optional parameter to your query function.
+  - Return `undefined` to indicate there is no next page available.
+- `getPreviousPageParam: (firstPage, allPages) => unknown | undefined`
+  - When new data is received for this query, this function receives both the first 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.
+  - Return `undefined` to indicate there is no previous page available.
 
 **Returns**
 
 The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery), with the addition of the following:
 
-- `isFetchingMore: false | 'next' | 'previous'`
-  - If using `paginated` mode, this will be `true` when fetching more results using the `fetchMore` function.
-- `fetchMore: (fetchMoreVariableOverride) => Promise<UseInfiniteQueryResult>`
+- `isFetchingNextPage: boolean`
+  - Will be `true` while fetching the next page with `fetchNextPage`.
+- `isFetchingPreviousPage: boolean`
+  - Will be `true` while fetching the previous page with `fetchPreviousPage`.
+- `fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>`
   - This function allows you to fetch the next "page" of results.
-  - `fetchMoreVariableOverride` allows you to optionally override the fetch more variable returned from your `getFetchMore` option to your query function to retrieve the next page of results.
-- `canFetchMore: boolean`
-  - If using `paginated` mode, this will be `true` if there is more data to be fetched (known via the required `getFetchMore` option function).
+  - `options.pageParam: unknown` allows you to manually specify a page param instead of using `getNextPageParam`.
+- `fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult>`
+  - This function allows you to fetch the previous "page" of results.
+  - `options.pageParam: unknown` allows you to manually specify a page param instead of using `getPreviousPageParam`.
+- `hasNextPage: boolean`
+  - This will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option).
+- `hasPreviousPage: boolean`
+  - This will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option).
diff --git a/examples/basic-graphql-request/src/index.js b/examples/basic-graphql-request/src/index.js
index fc7d37ffd..42311d85c 100644
--- a/examples/basic-graphql-request/src/index.js
+++ b/examples/basic-graphql-request/src/index.js
@@ -126,7 +126,7 @@ function usePost(postId) {
       return post;
     },
     {
-      enabled: postId,
+      enabled: !!postId,
     }
   );
 }
diff --git a/examples/basic/src/index.js b/examples/basic/src/index.js
index acffc7beb..a496d1426 100644
--- a/examples/basic/src/index.js
+++ b/examples/basic/src/index.js
@@ -101,7 +101,7 @@ const getPostById = async (key, id) => {
 
 function usePost(postId) {
   return useQuery(["post", postId], getPostById, {
-    enabled: postId,
+    enabled: !!postId,
   });
 }
 
diff --git a/examples/default-query-function/src/index.js b/examples/default-query-function/src/index.js
index a5dc96576..8139a3935 100644
--- a/examples/default-query-function/src/index.js
+++ b/examples/default-query-function/src/index.js
@@ -105,7 +105,7 @@ function Posts({ setPostId }) {
 function Post({ postId, setPostId }) {
   // You can even leave out the queryFn and just go straight into options
   const { status, data, error, isFetching } = useQuery(`/posts/${postId}`, {
-    enabled: postId,
+    enabled: !!postId,
   });
 
   return (
diff --git a/examples/load-more-infinite-scroll/pages/index.js b/examples/load-more-infinite-scroll/pages/index.js
index 85919ac4b..aa3e92d79 100755
--- a/examples/load-more-infinite-scroll/pages/index.js
+++ b/examples/load-more-infinite-scroll/pages/index.js
@@ -31,14 +31,14 @@ function Example() {
     data,
     error,
     isFetching,
-    isFetchingMore,
-    fetchMore,
-    canFetchMore,
+    isFetchingNextPage,
+    fetchNextPage,
+    hasNextPage,
   } = useInfiniteQuery(
     'projects',
-    async (key, nextId = 0) => {
-      const { data } = await axios.get('/api/projects?cursor=' + nextId)
-      return data
+    async (_key, nextId = 0) => {
+      const res = await axios.get('/api/projects?cursor=' + nextId)
+      return res.data
     },
     {
       getFetchMore: lastGroup => lastGroup.nextId,
@@ -49,8 +49,8 @@ function Example() {
 
   useIntersectionObserver({
     target: loadMoreButtonRef,
-    onIntersect: fetchMore,
-    enabled: canFetchMore,
+    onIntersect: fetchNextPage,
+    enabled: hasNextPage,
   })
 
   return (
@@ -81,18 +81,20 @@ function Example() {
           <div>
             <button
               ref={loadMoreButtonRef}
-              onClick={() => fetchMore()}
-              disabled={!canFetchMore || isFetchingMore}
+              onClick={() => fetchNextPage()}
+              disabled={!hasNextPage || isFetchingNextPage}
             >
-              {isFetchingMore
+              {isFetchingNextPage
                 ? 'Loading more...'
-                : canFetchMore
+                : hasNextPage
                 ? 'Load More'
                 : 'Nothing more to load'}
             </button>
           </div>
           <div>
-            {isFetching && !isFetchingMore ? 'Background Updating...' : null}
+            {isFetching && !isFetchingNextPage
+              ? 'Background Updating...'
+              : null}
           </div>
         </>
       )}
diff --git a/src/core/query.ts b/src/core/query.ts
index 99376eda2..3c8d3212f 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -17,7 +17,6 @@ import {
 } from './utils'
 import type {
   InitialDataFunction,
-  IsFetchingMoreValue,
   QueryFunction,
   QueryKey,
   QueryOptions,
@@ -38,15 +37,18 @@ export interface QueryConfig<TData, TError, TQueryFnData> {
 }
 
 export interface QueryState<TData, TError> {
-  canFetchMore?: boolean
-  data?: TData
+  data: TData | undefined
   dataUpdateCount: number
   error: TError | null
   errorUpdateCount: number
   failureCount: number
+  hasNextPage: boolean | undefined
+  hasPreviousPage: boolean | undefined
   isFetching: boolean
-  isFetchingMore: IsFetchingMoreValue
+  isFetchingNextPage: boolean
+  isFetchingPreviousPage: boolean
   isInvalidated: boolean
+  pageParams: unknown[] | undefined
   status: QueryStatus
   updatedAt: number
 }
@@ -57,11 +59,12 @@ export interface FetchOptions {
 }
 
 interface FetchMoreOptions {
-  fetchMoreVariable?: unknown
-  previous?: boolean
+  pageParam?: unknown
+  direction: 'forward' | 'backward'
 }
 
 interface SetDataOptions {
+  pageParams?: unknown[]
   updatedAt?: number
 }
 
@@ -71,13 +74,16 @@ interface FailedAction {
 
 interface FetchAction {
   type: 'fetch'
-  isFetchingMore?: IsFetchingMoreValue
+  isFetchingNextPage?: boolean
+  isFetchingPreviousPage?: boolean
 }
 
 interface SuccessAction<TData> {
-  type: 'success'
   data: TData | undefined
-  canFetchMore?: boolean
+  hasNextPage?: boolean
+  hasPreviousPage?: boolean
+  pageParams?: unknown[]
+  type: 'success'
   updatedAt?: number
 }
 
@@ -205,14 +211,13 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
       data = prevData as TData
     }
 
-    // Try to determine if more data can be fetched
-    const canFetchMore = hasMorePages(this.options, data)
-
     // Set data and mark it as cached
     this.dispatch({
-      type: 'success',
       data,
-      canFetchMore,
+      hasNextPage: hasNextPage(this.options, data),
+      hasPreviousPage: hasPreviousPage(this.options, data),
+      pageParams: options?.pageParams,
+      type: 'success',
       updatedAt: options?.updatedAt,
     })
 
@@ -363,31 +368,23 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
       ? this.startInfiniteFetch(this.options, params, fetchOptions)
       : this.startFetch(this.options, params)
 
-    this.promise = promise
-      .then(data => {
-        // Set success state
-        this.setData(data)
-
-        // Return data
-        return data
-      })
-      .catch(error => {
-        // Set error state if needed
-        if (!(isCancelledError(error) && error.silent)) {
-          this.dispatch({
-            type: 'error',
-            error,
-          })
-        }
+    this.promise = promise.catch(error => {
+      // Set error state if needed
+      if (!(isCancelledError(error) && error.silent)) {
+        this.dispatch({
+          type: 'error',
+          error,
+        })
+      }
 
-        // Log error
-        if (!isCancelledError(error)) {
-          getLogger().error(error)
-        }
+      // Log error
+      if (!isCancelledError(error)) {
+        getLogger().error(error)
+      }
 
-        // Propagate error
-        throw error
-      })
+      // Propagate error
+      throw error
+    })
 
     return this.promise
   }
@@ -406,7 +403,9 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
     }
 
     // Try to fetch the data
-    return this.tryFetchData(options, fetchData)
+    return this.tryFetchData(options, fetchData).then(data =>
+      this.setData(data)
+    )
   }
 
   private startInfiniteFetch(
@@ -414,63 +413,100 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
     params: unknown[],
     fetchOptions?: FetchOptions
   ): Promise<TData> {
+    const queryFn = options.queryFn || defaultQueryFn
     const fetchMore = fetchOptions?.fetchMore
-    const { previous, fetchMoreVariable } = fetchMore || {}
-    const isFetchingMore = fetchMore ? (previous ? 'previous' : 'next') : false
-    const prevPages: TQueryFnData[] = (this.state.data as any) || []
+    const pageParam = fetchMore?.pageParam
+    const isFetchingNextPage = fetchMore?.direction === 'forward'
+    const isFetchingPreviousPage = fetchMore?.direction === 'backward'
+    const oldPages = (this.state.data || []) as TQueryFnData[]
+    const oldPageParams = this.state.pageParams || []
+    let newPageParams = oldPageParams
 
     // Create function to fetch a page
     const fetchPage = (
       pages: TQueryFnData[],
-      prepend?: boolean,
-      cursor?: unknown
+      manual?: boolean,
+      param?: unknown,
+      previous?: boolean
     ): Promise<TQueryFnData[]> => {
-      const lastPage = getLastPage(pages, prepend)
-
-      if (
-        typeof cursor === 'undefined' &&
-        typeof lastPage !== 'undefined' &&
-        options.getFetchMore
-      ) {
-        cursor = options.getFetchMore(lastPage, pages)
-      }
-
-      if (!cursor && typeof lastPage !== 'undefined') {
+      if (typeof param === 'undefined' && !manual && pages.length) {
         return Promise.resolve(pages)
       }
 
-      const queryFn = options.queryFn || defaultQueryFn
-
       return Promise.resolve()
-        .then(() => queryFn(...params, cursor))
-        .then(page => (prepend ? [page, ...pages] : [...pages, page]))
+        .then(() => queryFn(...params, param))
+        .then(page => {
+          newPageParams = previous
+            ? [param, ...newPageParams]
+            : [...newPageParams, param]
+          return previous ? [page, ...pages] : [...pages, page]
+        })
     }
 
     // Create function to fetch the data
     const fetchData = (): Promise<TQueryFnData[]> => {
-      if (isFetchingMore) {
-        return fetchPage(prevPages, previous, fetchMoreVariable)
-      } else if (!prevPages.length) {
+      // Reset new page params
+      newPageParams = oldPageParams
+
+      // Fetch first page?
+      if (!oldPages.length) {
         return fetchPage([])
-      } else {
-        let promise = fetchPage([])
-        for (let i = 1; i < prevPages.length; i++) {
-          promise = promise.then(fetchPage)
-        }
-        return promise
       }
+
+      // Fetch next page?
+      if (isFetchingNextPage) {
+        const manual = typeof pageParam !== 'undefined'
+        const param = manual ? pageParam : getNextPageParam(options, oldPages)
+        return fetchPage(oldPages, manual, param)
+      }
+
+      // Fetch previous page?
+      if (isFetchingPreviousPage) {
+        const manual = typeof pageParam !== 'undefined'
+        const param = manual
+          ? pageParam
+          : getPreviousPageParam(options, oldPages)
+        return fetchPage(oldPages, manual, param, true)
+      }
+
+      // Refetch pages
+      newPageParams = []
+
+      const manual = typeof options.getNextPageParam === 'undefined'
+
+      // Fetch first page
+      let promise = fetchPage([], manual, oldPageParams[0])
+
+      // Fetch remaining pages
+      for (let i = 1; i < oldPages.length; i++) {
+        promise = promise.then(pages => {
+          const param = manual
+            ? oldPageParams[i]
+            : getNextPageParam(options, pages)
+          return fetchPage(pages, manual, param)
+        })
+      }
+
+      return promise
     }
 
     // Set to fetching state if not already in it
     if (
       !this.state.isFetching ||
-      this.state.isFetchingMore !== isFetchingMore
+      this.state.isFetchingNextPage !== isFetchingNextPage ||
+      this.state.isFetchingPreviousPage !== isFetchingPreviousPage
     ) {
-      this.dispatch({ type: 'fetch', isFetchingMore })
+      this.dispatch({
+        type: 'fetch',
+        isFetchingNextPage,
+        isFetchingPreviousPage,
+      })
     }
 
     // Try to get the data
-    return this.tryFetchData(options, fetchData)
+    return this.tryFetchData(options, fetchData).then(data =>
+      this.setData(data, { pageParams: newPageParams })
+    )
   }
 
   private tryFetchData(
@@ -596,21 +632,44 @@ function defaultQueryFn() {
   return Promise.reject()
 }
 
-function getLastPage<TQueryFnData>(
-  pages: TQueryFnData[],
-  previous?: boolean
-): TQueryFnData {
-  return previous ? pages[0] : pages[pages.length - 1]
+function getNextPageParam<TData, TError, TQueryFnData>(
+  options: QueryOptions<TData, TError, TQueryFnData>,
+  pages: TQueryFnData[]
+): unknown | undefined {
+  return options.getNextPageParam?.(pages[pages.length - 1], pages)
+}
+
+function getPreviousPageParam<TData, TError, TQueryFnData>(
+  options: QueryOptions<TData, TError, TQueryFnData>,
+  pages: TQueryFnData[]
+): unknown | undefined {
+  return options.getPreviousPageParam?.(pages[0], pages)
+}
+
+/**
+ * Checks if there is a next page.
+ * Returns `undefined` if it cannot be determined.
+ */
+function hasNextPage<TData, TError, TQueryFnData>(
+  options: QueryOptions<TData, TError, TQueryFnData>,
+  pages: unknown
+): boolean | undefined {
+  return options.getNextPageParam && Array.isArray(pages)
+    ? typeof getNextPageParam(options, pages) !== 'undefined'
+    : undefined
 }
 
-function hasMorePages<TData, TError, TQueryFnData>(
+/**
+ * Checks if there is a previous page.
+ * Returns `undefined` if it cannot be determined.
+ */
+function hasPreviousPage<TData, TError, TQueryFnData>(
   options: QueryOptions<TData, TError, TQueryFnData>,
-  pages: unknown,
-  previous?: boolean
+  pages: unknown
 ): boolean | undefined {
-  if (options.infinite && options.getFetchMore && Array.isArray(pages)) {
-    return Boolean(options.getFetchMore(getLastPage(pages, previous), pages))
-  }
+  return options.getPreviousPageParam && Array.isArray(pages)
+    ? typeof getPreviousPageParam(options, pages) !== 'undefined'
+    : undefined
 }
 
 function getDefaultState<TData, TError, TQueryFnData>(
@@ -624,15 +683,18 @@ function getDefaultState<TData, TError, TQueryFnData>(
   const hasData = typeof data !== 'undefined'
 
   return {
-    canFetchMore: hasMorePages(options, data),
     data,
     dataUpdateCount: 0,
     error: null,
     errorUpdateCount: 0,
     failureCount: 0,
+    hasNextPage: hasNextPage(options, data),
+    hasPreviousPage: hasPreviousPage(options, data),
     isFetching: false,
-    isFetchingMore: false,
+    isFetchingNextPage: false,
+    isFetchingPreviousPage: false,
     isInvalidated: false,
+    pageParams: undefined,
     status: hasData ? 'success' : 'idle',
     updatedAt: hasData ? Date.now() : 0,
   }
@@ -653,19 +715,23 @@ export function queryReducer<TData, TError>(
         ...state,
         failureCount: 0,
         isFetching: true,
-        isFetchingMore: action.isFetchingMore || false,
+        isFetchingNextPage: action.isFetchingNextPage || false,
+        isFetchingPreviousPage: action.isFetchingPreviousPage || false,
         status: state.updatedAt ? 'success' : 'loading',
       }
     case 'success':
       return {
         ...state,
-        canFetchMore: action.canFetchMore,
         data: action.data,
         dataUpdateCount: state.dataUpdateCount + 1,
         error: null,
         failureCount: 0,
+        hasNextPage: action.hasNextPage,
+        hasPreviousPage: action.hasPreviousPage,
+        pageParams: action.pageParams,
         isFetching: false,
-        isFetchingMore: false,
+        isFetchingNextPage: false,
+        isFetchingPreviousPage: false,
         isInvalidated: false,
         status: 'success',
         updatedAt: action.updatedAt ?? Date.now(),
@@ -677,7 +743,8 @@ export function queryReducer<TData, TError>(
         errorUpdateCount: state.errorUpdateCount + 1,
         failureCount: state.failureCount + 1,
         isFetching: false,
-        isFetchingMore: false,
+        isFetchingNextPage: false,
+        isFetchingPreviousPage: false,
         status: 'error',
       }
     case 'invalidate':
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index eb517f662..61c971507 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -10,7 +10,8 @@ import {
 } from './utils'
 import { notifyManager } from './notifyManager'
 import type {
-  FetchMoreOptions,
+  FetchNextPageOptions,
+  FetchPreviousPageOptions,
   QueryObserverOptions,
   QueryObserverResult,
   QueryOptions,
@@ -70,7 +71,8 @@ export class QueryObserver<
     // Bind exposed methods
     this.remove = this.remove.bind(this)
     this.refetch = this.refetch.bind(this)
-    this.fetchMore = this.fetchMore.bind(this)
+    this.fetchNextPage = this.fetchNextPage.bind(this)
+    this.fetchPreviousPage = this.fetchPreviousPage.bind(this)
 
     // Subscribe to the query
     this.updateQuery()
@@ -222,13 +224,21 @@ export class QueryObserver<
     return this.fetch(options)
   }
 
-  fetchMore(
-    fetchMoreVariable?: unknown,
-    options?: FetchMoreOptions
+  fetchNextPage(
+    options?: FetchNextPageOptions
   ): Promise<QueryObserverResult<TData, TError>> {
     return this.fetch({
       throwOnError: options?.throwOnError,
-      fetchMore: { previous: options?.previous, fetchMoreVariable },
+      fetchMore: { direction: 'forward', pageParam: options?.pageParam },
+    })
+  }
+
+  fetchPreviousPage(
+    options?: FetchPreviousPageOptions
+  ): Promise<QueryObserverResult<TData, TError>> {
+    return this.fetch({
+      throwOnError: options?.throwOnError,
+      fetchMore: { direction: 'backward', pageParam: options?.pageParam },
     })
   }
 
@@ -371,15 +381,18 @@ export class QueryObserver<
 
     const result: QueryObserverResult<TData, TError> = {
       ...getStatusProps(status),
-      canFetchMore: state.canFetchMore,
       data,
       error: state.error,
       failureCount: state.failureCount,
-      fetchMore: this.fetchMore,
+      fetchNextPage: this.fetchNextPage,
+      fetchPreviousPage: this.fetchPreviousPage,
+      hasNextPage: state.hasNextPage,
+      hasPreviousPage: state.hasPreviousPage,
       isFetched: state.dataUpdateCount > 0,
       isFetchedAfterMount: state.dataUpdateCount > this.initialDataUpdateCount,
       isFetching,
-      isFetchingMore: state.isFetchingMore,
+      isFetchingNextPage: state.isFetchingNextPage,
+      isFetchingPreviousPage: state.isFetchingPreviousPage,
       isPreviousData,
       isStale: this.isStale(),
       refetch: this.refetch,
diff --git a/src/core/types.ts b/src/core/types.ts
index 8f085aa0f..1d8e0e79b 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -15,10 +15,15 @@ export type ShouldRetryFunction<TError = unknown> = (
   error: TError
 ) => boolean
 
-export type GetFetchMoreVariableFunction<TQueryFnData = unknown> = (
+export type GetPreviousPageParamFunction<TQueryFnData = unknown> = (
+  firstPage: TQueryFnData,
+  allPages: TQueryFnData[]
+) => unknown | undefined
+
+export type GetNextPageParamFunction<TQueryFnData = unknown> = (
   lastPage: TQueryFnData,
   allPages: TQueryFnData[]
-) => unknown
+) => unknown | undefined
 
 export type RetryDelayFunction = (attempt: number) => number
 
@@ -49,11 +54,16 @@ export interface QueryOptions<
    * Defaults to `true`.
    */
   structuralSharing?: boolean
+  /**
+   * This function can be set to automatically get the previous cursor for infinite queries.
+   * The result will also be used to determine the value of `hasPreviousPage`.
+   */
+  getPreviousPageParam?: GetPreviousPageParamFunction<TQueryFnData>
   /**
    * This function can be set to automatically get the next cursor for infinite queries.
-   * The result will also be used to determine the value of `canFetchMore`.
+   * The result will also be used to determine the value of `hasNextPage`.
    */
-  getFetchMore?: GetFetchMoreVariableFunction<TQueryFnData>
+  getNextPageParam?: GetNextPageParamFunction<TQueryFnData>
 }
 
 export interface FetchQueryOptions<
@@ -166,28 +176,34 @@ export interface InvalidateOptions {
   throwOnError?: boolean
 }
 
-export interface FetchMoreOptions extends ResultOptions {
-  previous?: boolean
+export interface FetchNextPageOptions extends ResultOptions {
+  pageParam?: unknown
 }
 
-export type IsFetchingMoreValue = 'previous' | 'next' | false
+export interface FetchPreviousPageOptions extends ResultOptions {
+  pageParam?: unknown
+}
 
 export type QueryStatus = 'idle' | 'loading' | 'error' | 'success'
 
 export interface QueryObserverResult<TData = unknown, TError = unknown> {
-  canFetchMore: boolean | undefined
   data: TData | undefined
   error: TError | null
   failureCount: number
-  fetchMore: (
-    fetchMoreVariable?: unknown,
-    options?: FetchMoreOptions
+  fetchNextPage: (
+    options?: FetchNextPageOptions
+  ) => Promise<QueryObserverResult<TData, TError>>
+  fetchPreviousPage: (
+    options?: FetchPreviousPageOptions
   ) => Promise<QueryObserverResult<TData, TError>>
+  hasNextPage?: boolean
+  hasPreviousPage?: boolean
   isError: boolean
   isFetched: boolean
   isFetchedAfterMount: boolean
   isFetching: boolean
-  isFetchingMore?: IsFetchingMoreValue
+  isFetchingNextPage?: boolean
+  isFetchingPreviousPage?: boolean
   isIdle: boolean
   isLoading: boolean
   isPreviousData: boolean


Test patch:
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index 102726d9d..3eafb5b1f 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -17,29 +17,24 @@ import {
 
 interface Result {
   items: number[]
-  nextId: number
+  nextId?: number
+  prevId?: number
   ts: number
 }
 
 const pageSize = 10
 
-const initialItems = (page: number): Result => {
-  return {
-    items: [...new Array(10)].fill(null).map((_, d) => page * pageSize + d),
-    nextId: page + 1,
-    ts: page,
-  }
-}
-
 const fetchItems = async (
   page: number,
   ts: number,
-  nextId?: any
+  noNext?: boolean,
+  noPrev?: boolean
 ): Promise<Result> => {
   await sleep(10)
   return {
     items: [...new Array(10)].fill(null).map((_, d) => page * pageSize + d),
-    nextId: nextId ?? page + 1,
+    nextId: noNext ? undefined : page + 1,
+    prevId: noPrev ? undefined : page - 1,
     ts,
   }
 }
@@ -50,43 +45,39 @@ describe('useInfiniteQuery', () => {
 
   it('should return the correct states for a successful query', async () => {
     const key = queryKey()
-
-    let count = 0
-    const states: UseInfiniteQueryResult<Result>[] = []
+    const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
       const state = useInfiniteQuery(
         key,
-        (_key: string, nextId: number = 0) => fetchItems(nextId, count++),
+        (_key: string, pageParam: number = 0) => pageParam,
         {
-          getFetchMore: (lastGroup, _allGroups) => lastGroup.nextId,
+          getNextPageParam: lastPage => lastPage + 1,
         }
       )
-
       states.push(state)
-
-      return (
-        <div>
-          <h1>Status: {state.status}</h1>
-        </div>
-      )
+      return null
     }
 
-    const rendered = renderWithClient(client, <Page />)
+    renderWithClient(client, <Page />)
 
-    await waitFor(() => rendered.getByText('Status: success'))
+    await sleep(100)
 
+    expect(states.length).toBe(2)
     expect(states[0]).toEqual({
-      canFetchMore: undefined,
       data: undefined,
       error: null,
       failureCount: 0,
-      fetchMore: expect.any(Function),
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isError: false,
       isFetched: false,
       isFetchedAfterMount: false,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: true,
       isPreviousData: false,
@@ -99,22 +90,19 @@ describe('useInfiniteQuery', () => {
     })
 
     expect(states[1]).toEqual({
-      canFetchMore: true,
-      data: [
-        {
-          items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
-          nextId: 1,
-          ts: 0,
-        },
-      ],
+      data: [0],
       error: null,
       failureCount: 0,
-      fetchMore: expect.any(Function),
-      isFetchingMore: false,
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: true,
+      hasPreviousPage: undefined,
       isError: false,
       isFetched: true,
       isFetchedAfterMount: true,
       isFetching: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: false,
       isPreviousData: false,
@@ -127,7 +115,7 @@ describe('useInfiniteQuery', () => {
     })
   })
 
-  it('should not throw when fetchMore returns an error', async () => {
+  it('should not throw when fetchNextPage returns an error', async () => {
     const consoleMock = mockConsoleError()
     const key = queryKey()
     let noThrow: boolean
@@ -136,30 +124,30 @@ describe('useInfiniteQuery', () => {
       const start = 1
       const state = useInfiniteQuery(
         key,
-        async (_key, page: number = start) => {
-          if (page === 2) {
+        async (_key, pageParam: number = start) => {
+          if (pageParam === 2) {
             throw new Error('error')
           }
-          return page
+          return pageParam
         },
         {
           retry: 1,
           retryDelay: 10,
-          getFetchMore: (lastPage, _pages) => lastPage + 1,
+          getNextPageParam: lastPage => lastPage + 1,
         }
       )
 
-      const { fetchMore } = state
+      const { fetchNextPage } = state
 
       React.useEffect(() => {
         setActTimeout(() => {
-          fetchMore()
+          fetchNextPage()
             .then(() => {
               noThrow = true
             })
             .catch(() => undefined)
         }, 20)
-      }, [fetchMore])
+      }, [fetchNextPage])
 
       return null
     }
@@ -179,28 +167,28 @@ describe('useInfiniteQuery', () => {
 
       const state = useInfiniteQuery(
         [key, order],
-        async (_key, orderArg, pageArg = 0) => {
+        async (_key, orderParam, pageParam = 0) => {
           await sleep(10)
-          return `${pageArg}-${orderArg}`
+          return `${pageParam}-${orderParam}`
         },
         {
-          getFetchMore: (_lastGroup, _allGroups) => 1,
+          getNextPageParam: () => 1,
           keepPreviousData: true,
         }
       )
 
       states.push(state)
 
-      const { fetchMore } = state
+      const { fetchNextPage } = state
 
       React.useEffect(() => {
         setActTimeout(() => {
-          fetchMore()
+          fetchNextPage()
         }, 50)
         setActTimeout(() => {
           setOrder('asc')
         }, 100)
-      }, [fetchMore])
+      }, [fetchNextPage])
 
       return null
     }
@@ -213,28 +201,28 @@ describe('useInfiniteQuery', () => {
     expect(states[0]).toMatchObject({
       data: undefined,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: false,
       isPreviousData: false,
     })
     expect(states[1]).toMatchObject({
       data: ['0-desc'],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
       isPreviousData: false,
     })
     expect(states[2]).toMatchObject({
       data: ['0-desc'],
       isFetching: true,
-      isFetchingMore: 'next',
+      isFetchingNextPage: true,
       isSuccess: true,
       isPreviousData: false,
     })
     expect(states[3]).toMatchObject({
       data: ['0-desc', '1-desc'],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
       isPreviousData: false,
     })
@@ -242,21 +230,21 @@ describe('useInfiniteQuery', () => {
     expect(states[4]).toMatchObject({
       data: ['0-desc', '1-desc'],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
       isPreviousData: false,
     })
     expect(states[5]).toMatchObject({
       data: ['0-desc', '1-desc'],
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
       isPreviousData: true,
     })
     expect(states[6]).toMatchObject({
       data: ['0-asc'],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
       isPreviousData: false,
     })
@@ -289,7 +277,56 @@ describe('useInfiniteQuery', () => {
     })
   })
 
-  it('should prepend pages when the previous option is set to true', async () => {
+  it('should be able to reverse the data', async () => {
+    const key = queryKey()
+    const states: UseInfiniteQueryResult<number>[] = []
+
+    function Page() {
+      const state = useInfiniteQuery(
+        key,
+        (_key, pageParam: number = 0) => pageParam,
+        {
+          select: pages => [...pages].reverse(),
+        }
+      )
+
+      states.push(state)
+
+      const { fetchNextPage } = state
+
+      React.useEffect(() => {
+        setActTimeout(() => {
+          fetchNextPage({ pageParam: 1 })
+        }, 10)
+      }, [fetchNextPage])
+
+      return null
+    }
+
+    renderWithClient(client, <Page />)
+
+    await sleep(100)
+
+    expect(states.length).toBe(4)
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isSuccess: false,
+    })
+    expect(states[1]).toMatchObject({
+      data: [0],
+      isSuccess: true,
+    })
+    expect(states[2]).toMatchObject({
+      data: [0],
+      isSuccess: true,
+    })
+    expect(states[3]).toMatchObject({
+      data: [1, 0],
+      isSuccess: true,
+    })
+  })
+
+  it('should be able to fetch a previous page', async () => {
     const key = queryKey()
     const states: UseInfiniteQueryResult<number>[] = []
 
@@ -297,24 +334,24 @@ describe('useInfiniteQuery', () => {
       const start = 10
       const state = useInfiniteQuery(
         key,
-        async (_key, page: number = start) => {
+        async (_key, pageParam: number = start) => {
           await sleep(10)
-          return page
+          return pageParam
         },
         {
-          getFetchMore: (lastPage, _pages) => lastPage - 1,
+          getPreviousPageParam: firstPage => firstPage - 1,
         }
       )
 
       states.push(state)
 
-      const { fetchMore } = state
+      const { fetchPreviousPage } = state
 
       React.useEffect(() => {
         setActTimeout(() => {
-          fetchMore(undefined, { previous: true })
+          fetchPreviousPage()
         }, 20)
-      }, [fetchMore])
+      }, [fetchPreviousPage])
 
       return null
     }
@@ -325,35 +362,223 @@ describe('useInfiniteQuery', () => {
 
     expect(states.length).toBe(4)
     expect(states[0]).toMatchObject({
-      canFetchMore: undefined,
       data: undefined,
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isSuccess: false,
     })
     expect(states[1]).toMatchObject({
-      canFetchMore: true,
       data: [10],
+      hasNextPage: undefined,
+      hasPreviousPage: true,
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isSuccess: true,
     })
     expect(states[2]).toMatchObject({
-      canFetchMore: true,
       data: [10],
+      hasNextPage: undefined,
+      hasPreviousPage: true,
       isFetching: true,
-      isFetchingMore: 'previous',
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: true,
       isSuccess: true,
     })
     expect(states[3]).toMatchObject({
-      canFetchMore: true,
       data: [9, 10],
+      hasNextPage: undefined,
+      hasPreviousPage: true,
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isSuccess: true,
     })
   })
 
+  it('should be able to refetch when providing page params manually', async () => {
+    const key = queryKey()
+    const states: UseInfiniteQueryResult<number>[] = []
+
+    function Page() {
+      const state = useInfiniteQuery(
+        key,
+        (_key, pageParam: number = 10) => pageParam
+      )
+
+      states.push(state)
+
+      const { fetchNextPage, fetchPreviousPage, refetch } = state
+
+      React.useEffect(() => {
+        setActTimeout(() => {
+          fetchNextPage({ pageParam: 11 })
+        }, 10)
+        setActTimeout(() => {
+          fetchPreviousPage({ pageParam: 9 })
+        }, 20)
+        setActTimeout(() => {
+          refetch()
+        }, 30)
+      }, [fetchNextPage, fetchPreviousPage, refetch])
+
+      return null
+    }
+
+    renderWithClient(client, <Page />)
+
+    await sleep(100)
+
+    expect(states.length).toBe(8)
+    // Initial fetch
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isFetching: true,
+      isFetchingNextPage: false,
+    })
+    // Initial fetch done
+    expect(states[1]).toMatchObject({
+      data: [10],
+      isFetching: false,
+      isFetchingNextPage: false,
+    })
+    // Fetch next page
+    expect(states[2]).toMatchObject({
+      data: [10],
+      isFetching: true,
+      isFetchingNextPage: true,
+    })
+    // Fetch next page done
+    expect(states[3]).toMatchObject({
+      data: [10, 11],
+      isFetching: false,
+      isFetchingNextPage: false,
+    })
+    // Fetch previous page
+    expect(states[4]).toMatchObject({
+      data: [10, 11],
+      isFetching: true,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: true,
+    })
+    // Fetch previous page done
+    expect(states[5]).toMatchObject({
+      data: [9, 10, 11],
+      isFetching: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
+    })
+    // Refetch
+    expect(states[6]).toMatchObject({
+      data: [9, 10, 11],
+      isFetching: true,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
+    })
+    // Refetch done
+    expect(states[7]).toMatchObject({
+      data: [9, 10, 11],
+      isFetching: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
+    })
+  })
+
+  it('should be able to refetch when providing page params automatically', async () => {
+    const key = queryKey()
+    const states: UseInfiniteQueryResult<number>[] = []
+
+    function Page() {
+      const state = useInfiniteQuery(
+        key,
+        (_key, pageParam: number = 10) => pageParam,
+        {
+          getPreviousPageParam: firstPage => firstPage - 1,
+          getNextPageParam: lastPage => lastPage + 1,
+        }
+      )
+
+      states.push(state)
+
+      const { fetchNextPage, fetchPreviousPage, refetch } = state
+
+      React.useEffect(() => {
+        setActTimeout(() => {
+          fetchNextPage()
+        }, 10)
+        setActTimeout(() => {
+          fetchPreviousPage()
+        }, 20)
+        setActTimeout(() => {
+          refetch()
+        }, 30)
+      }, [fetchNextPage, fetchPreviousPage, refetch])
+
+      return null
+    }
+
+    renderWithClient(client, <Page />)
+
+    await sleep(100)
+
+    expect(states.length).toBe(8)
+    // Initial fetch
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isFetching: true,
+      isFetchingNextPage: false,
+    })
+    // Initial fetch done
+    expect(states[1]).toMatchObject({
+      data: [10],
+      isFetching: false,
+      isFetchingNextPage: false,
+    })
+    // Fetch next page
+    expect(states[2]).toMatchObject({
+      data: [10],
+      isFetching: true,
+      isFetchingNextPage: true,
+    })
+    // Fetch next page done
+    expect(states[3]).toMatchObject({
+      data: [10, 11],
+      isFetching: false,
+      isFetchingNextPage: false,
+    })
+    // Fetch previous page
+    expect(states[4]).toMatchObject({
+      data: [10, 11],
+      isFetching: true,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: true,
+    })
+    // Fetch previous page done
+    expect(states[5]).toMatchObject({
+      data: [9, 10, 11],
+      isFetching: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
+    })
+    // Refetch
+    expect(states[6]).toMatchObject({
+      data: [9, 10, 11],
+      isFetching: true,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
+    })
+    // Refetch done
+    expect(states[7]).toMatchObject({
+      data: [9, 10, 11],
+      isFetching: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
+    })
+  })
+
   it('should silently cancel any ongoing fetch when fetching more', async () => {
     const key = queryKey()
     const states: UseInfiniteQueryResult<number>[] = []
@@ -362,27 +587,27 @@ describe('useInfiniteQuery', () => {
       const start = 10
       const state = useInfiniteQuery(
         key,
-        async (_key, page: number = start) => {
+        async (_key, pageParam: number = start) => {
           await sleep(50)
-          return page
+          return pageParam
         },
         {
-          getFetchMore: (lastPage, _pages) => lastPage + 1,
+          getNextPageParam: lastPage => lastPage + 1,
         }
       )
 
       states.push(state)
 
-      const { refetch, fetchMore } = state
+      const { refetch, fetchNextPage } = state
 
       React.useEffect(() => {
         setActTimeout(() => {
           refetch()
         }, 100)
         setActTimeout(() => {
-          fetchMore()
+          fetchNextPage()
         }, 110)
-      }, [fetchMore, refetch])
+      }, [fetchNextPage, refetch])
 
       return null
     }
@@ -393,38 +618,38 @@ describe('useInfiniteQuery', () => {
 
     expect(states.length).toBe(5)
     expect(states[0]).toMatchObject({
-      canFetchMore: undefined,
+      hasNextPage: undefined,
       data: undefined,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: false,
     })
     expect(states[1]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [10],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     expect(states[2]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [10],
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     expect(states[3]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [10],
       isFetching: true,
-      isFetchingMore: 'next',
+      isFetchingNextPage: true,
       isSuccess: true,
     })
     expect(states[4]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [10, 11],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
   })
@@ -437,24 +662,24 @@ describe('useInfiniteQuery', () => {
       const start = 10
       const state = useInfiniteQuery(
         key,
-        async (_key, page: number = start) => {
+        async (_key, pageParam: number = start) => {
           await sleep(50)
-          return page
+          return pageParam
         },
         {
-          getFetchMore: (lastPage, _pages) => lastPage + 1,
+          getNextPageParam: lastPage => lastPage + 1,
         }
       )
 
       states.push(state)
 
-      const { refetch, fetchMore } = state
+      const { fetchNextPage } = state
 
       React.useEffect(() => {
         setActTimeout(() => {
-          fetchMore()
+          fetchNextPage()
         }, 10)
-      }, [fetchMore, refetch])
+      }, [fetchNextPage])
 
       return null
     }
@@ -465,46 +690,46 @@ describe('useInfiniteQuery', () => {
 
     expect(states.length).toBe(2)
     expect(states[0]).toMatchObject({
-      canFetchMore: undefined,
+      hasNextPage: undefined,
       data: undefined,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: false,
     })
     expect(states[1]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [10],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
   })
 
-  it('should be able to override the cursor in the fetchMore callback', async () => {
+  it('should be able to override the cursor in the fetchNextPage callback', async () => {
     const key = queryKey()
     const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
       const state = useInfiniteQuery(
         key,
-        async (_key, page: number = 0) => {
+        async (_key, pageParam: number = 0) => {
           await sleep(10)
-          return page
+          return pageParam
         },
         {
-          getFetchMore: (lastPage, _pages) => lastPage + 1,
+          getNextPageParam: lastPage => lastPage + 1,
         }
       )
 
       states.push(state)
 
-      const { fetchMore } = state
+      const { fetchNextPage } = state
 
       React.useEffect(() => {
         setActTimeout(() => {
-          fetchMore(5)
+          fetchNextPage({ pageParam: 5 })
         }, 20)
-      }, [fetchMore])
+      }, [fetchNextPage])
 
       return null
     }
@@ -515,31 +740,31 @@ describe('useInfiniteQuery', () => {
 
     expect(states.length).toBe(4)
     expect(states[0]).toMatchObject({
-      canFetchMore: undefined,
+      hasNextPage: undefined,
       data: undefined,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: false,
     })
     expect(states[1]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [0],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     expect(states[2]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [0],
       isFetching: true,
-      isFetchingMore: 'next',
+      isFetchingNextPage: true,
       isSuccess: true,
     })
     expect(states[3]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [0, 5],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
   })
@@ -553,12 +778,12 @@ describe('useInfiniteQuery', () => {
 
       const state = useInfiniteQuery(
         key,
-        async (_key, page: number = firstPage) => {
+        async (_key, pageParam: number = firstPage) => {
           await sleep(10)
-          return page
+          return pageParam
         },
         {
-          getFetchMore: (lastPage, _pages) => lastPage + 1,
+          getNextPageParam: lastPage => lastPage + 1,
         }
       )
 
@@ -586,401 +811,159 @@ describe('useInfiniteQuery', () => {
 
     expect(states.length).toBe(6)
     expect(states[0]).toMatchObject({
-      canFetchMore: undefined,
+      hasNextPage: undefined,
       data: undefined,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: false,
     })
     // After first fetch
     expect(states[1]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [0],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     // Set state
     expect(states[2]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [0],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     // Cache update
     expect(states[3]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [7, 8],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     // Refetch
     expect(states[4]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [7, 8],
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
     // Refetch done
     expect(states[5]).toMatchObject({
-      canFetchMore: true,
+      hasNextPage: true,
       data: [7, 8],
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
       isSuccess: true,
     })
   })
 
-  it('should allow you to fetch more pages', async () => {
+  it('should set hasNextPage to false if getNextPageParam returns undefined', async () => {
     const key = queryKey()
+    const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const fetchCountRef = React.useRef(0)
-      const {
-        status,
-        data,
-        error,
-        isFetching,
-        isFetchingMore,
-        fetchMore,
-        canFetchMore,
-        refetch,
-      } = useInfiniteQuery<Result, Error>(
-        key,
-        (_key, nextId = 0) => fetchItems(nextId, fetchCountRef.current++),
-        {
-          getFetchMore: (lastGroup, _allGroups) => lastGroup.nextId,
-        }
-      )
-
-      return (
-        <div>
-          <h1>Pagination</h1>
-          {status === 'loading' ? (
-            'Loading...'
-          ) : status === 'error' ? (
-            <span>Error: {error?.message}</span>
-          ) : (
-            <>
-              <div>Data:</div>
-              {data?.map((page, i) => (
-                <div key={i}>
-                  <div>
-                    Page {i}: {page.ts}
-                  </div>
-                  <div key={i}>
-                    {page.items.map(item => (
-                      <p key={item}>Item: {item}</p>
-                    ))}
-                  </div>
-                </div>
-              ))}
-              <div>
-                <button
-                  onClick={() => fetchMore()}
-                  disabled={!canFetchMore || Boolean(isFetchingMore)}
-                >
-                  {isFetchingMore
-                    ? 'Loading more...'
-                    : canFetchMore
-                    ? 'Load More'
-                    : 'Nothing more to load'}
-                </button>
-                <button onClick={() => refetch()}>Refetch</button>
-              </div>
-              <div>
-                {isFetching && !isFetchingMore
-                  ? 'Background Updating...'
-                  : null}
-              </div>
-            </>
-          )}
-        </div>
-      )
-    }
-
-    const rendered = renderWithClient(client, <Page />)
+      const state = useInfiniteQuery(key, (_key, pageParam = 1) => pageParam, {
+        getNextPageParam: () => undefined,
+      })
 
-    rendered.getByText('Loading...')
+      states.push(state)
 
-    await waitFor(() => {
-      rendered.getByText('Item: 9')
-      rendered.getByText('Page 0: 0')
-    })
+      return null
+    }
 
-    fireEvent.click(rendered.getByText('Load More'))
+    renderWithClient(client, <Page />)
 
-    await waitFor(() => rendered.getByText('Loading more...'))
+    await sleep(100)
 
-    await waitFor(() => {
-      rendered.getByText('Item: 19')
-      rendered.getByText('Page 0: 0')
-      rendered.getByText('Page 1: 1')
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      hasNextPage: undefined,
+      isFetching: true,
+      isFetchingNextPage: false,
+      isSuccess: false,
     })
-
-    fireEvent.click(rendered.getByText('Refetch'))
-
-    await waitFor(() => rendered.getByText('Background Updating...'))
-    await waitFor(() => {
-      rendered.getByText('Item: 19')
-      rendered.getByText('Page 0: 2')
-      rendered.getByText('Page 1: 3')
+    expect(states[1]).toMatchObject({
+      data: [1],
+      hasNextPage: false,
+      isFetching: false,
+      isFetchingNextPage: false,
+      isSuccess: true,
     })
   })
 
-  it('should compute canFetchMore correctly for falsy getFetchMore return value', async () => {
+  it('should compute hasNextPage correctly using initialData', async () => {
     const key = queryKey()
+    const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const fetchCountRef = React.useRef(0)
-      const {
-        status,
-        data,
-        error,
-        isFetching,
-        isFetchingMore,
-        fetchMore,
-        canFetchMore,
-        refetch,
-      } = useInfiniteQuery<Result, Error, [string, number]>(
-        key,
-        (_key, nextId = 0) => fetchItems(nextId, fetchCountRef.current++),
-        {
-          getFetchMore: (_lastGroup, _allGroups) => undefined,
-        }
-      )
-
-      return (
-        <div>
-          <h1>Pagination</h1>
-          {status === 'loading' ? (
-            'Loading...'
-          ) : status === 'error' ? (
-            <span>Error: {error?.message}</span>
-          ) : (
-            <>
-              <div>Data:</div>
-              {data?.map((page, i) => (
-                <div key={i}>
-                  <div>
-                    Page {i}: {page.ts}
-                  </div>
-                  <div key={i}>
-                    {page.items.map(item => (
-                      <p key={item}>Item: {item}</p>
-                    ))}
-                  </div>
-                </div>
-              ))}
-              <div>
-                <button
-                  onClick={() => fetchMore()}
-                  disabled={!canFetchMore || Boolean(isFetchingMore)}
-                >
-                  {isFetchingMore
-                    ? 'Loading more...'
-                    : canFetchMore
-                    ? 'Load More'
-                    : 'Nothing more to load'}
-                </button>
-                <button onClick={() => refetch()}>Refetch</button>
-              </div>
-              <div>
-                {isFetching && !isFetchingMore
-                  ? 'Background Updating...'
-                  : null}
-              </div>
-            </>
-          )}
-        </div>
-      )
-    }
-
-    const rendered = renderWithClient(client, <Page />)
-
-    rendered.getByText('Loading...')
-
-    await waitFor(() => {
-      rendered.getByText('Item: 9')
-      rendered.getByText('Page 0: 0')
-    })
-
-    rendered.getByText('Nothing more to load')
-  })
-
-  it('should compute canFetchMore correctly using initialData', async () => {
-    const key = queryKey()
+      const state = useInfiniteQuery(key, (_key, pageParam = 10) => pageParam, {
+        initialData: [10],
+        getNextPageParam: lastPage => (lastPage === 10 ? 11 : undefined),
+      })
 
-    function Page() {
-      const fetchCountRef = React.useRef(0)
-      const {
-        status,
-        data,
-        error,
-        isFetching,
-        isFetchingMore,
-        fetchMore,
-        canFetchMore,
-        refetch,
-      } = useInfiniteQuery<Result, Error>(
-        key,
-        (_key, nextId = 0) => fetchItems(nextId, fetchCountRef.current++),
-        {
-          staleTime: 1000,
-          initialData: [initialItems(0)],
-          getFetchMore: (lastGroup, _allGroups) => lastGroup.nextId,
-        }
-      )
+      states.push(state)
 
-      return (
-        <div>
-          <h1>Pagination</h1>
-          {status === 'loading' ? (
-            'Loading...'
-          ) : status === 'error' ? (
-            <span>Error: {error?.message}</span>
-          ) : (
-            <>
-              <div>Data:</div>
-              {data?.map((page, i) => (
-                <div key={i}>
-                  <div>
-                    Page {i}: {page.ts}
-                  </div>
-                  <div key={i}>
-                    {page.items.map(item => (
-                      <p key={item}>Item: {item}</p>
-                    ))}
-                  </div>
-                </div>
-              ))}
-              <div>
-                <button
-                  onClick={() => fetchMore()}
-                  disabled={!canFetchMore || Boolean(isFetchingMore)}
-                >
-                  {isFetchingMore
-                    ? 'Loading more...'
-                    : canFetchMore
-                    ? 'Load More'
-                    : 'Nothing more to load'}
-                </button>
-                <button onClick={() => refetch()}>Refetch</button>
-              </div>
-              <div>
-                {isFetching && !isFetchingMore
-                  ? 'Background Updating...'
-                  : null}
-              </div>
-            </>
-          )}
-        </div>
-      )
+      return null
     }
 
-    const rendered = renderWithClient(client, <Page />)
-
-    rendered.getByText('Item: 9')
-    rendered.getByText('Page 0: 0')
-
-    fireEvent.click(rendered.getByText('Load More'))
+    renderWithClient(client, <Page />)
 
-    await waitFor(() => rendered.getByText('Loading more...'))
+    await sleep(100)
 
-    await waitFor(() => {
-      rendered.getByText('Item: 19')
-      rendered.getByText('Page 1: 0')
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({
+      data: [10],
+      hasNextPage: true,
+      isFetching: true,
+      isFetchingNextPage: false,
+      isSuccess: true,
     })
-
-    fireEvent.click(rendered.getByText('Refetch'))
-
-    await waitFor(() => rendered.getByText('Background Updating...'))
-    await waitFor(() => {
-      rendered.getByText('Item: 19')
-      rendered.getByText('Page 0: 1')
-      rendered.getByText('Page 1: 2')
+    expect(states[1]).toMatchObject({
+      data: [10],
+      hasNextPage: true,
+      isFetching: false,
+      isFetchingNextPage: false,
+      isSuccess: true,
     })
   })
 
-  it('should compute canFetchMore correctly for falsy getFetchMore return value using initialData', async () => {
+  it('should compute hasNextPage correctly for falsy getFetchMore return value using initialData', async () => {
     const key = queryKey()
+    const states: UseInfiniteQueryResult<number>[] = []
 
     function Page() {
-      const fetchCountRef = React.useRef(0)
-      const {
-        status,
-        data,
-        error,
-        isFetching,
-        isFetchingMore,
-        fetchMore,
-        canFetchMore,
-        refetch,
-      } = useInfiniteQuery<Result, Error, [string, number]>(
-        key,
-        (_key, nextId = 0) => fetchItems(nextId, fetchCountRef.current++),
-        {
-          staleTime: 1000,
-          initialData: [initialItems(0)],
-          getFetchMore: (_lastGroup, _allGroups) => undefined,
-        }
-      )
+      const state = useInfiniteQuery(key, (_key, pageParam = 10) => pageParam, {
+        initialData: [10],
+        getNextPageParam: () => undefined,
+      })
 
-      return (
-        <div>
-          <h1>Pagination</h1>
-          {status === 'loading' ? (
-            'Loading...'
-          ) : status === 'error' ? (
-            <span>Error: {error?.message}</span>
-          ) : (
-            <>
-              <div>Data:</div>
-              {data?.map((page, i) => (
-                <div key={i}>
-                  <div>
-                    Page {i}: {page.ts}
-                  </div>
-                  <div key={i}>
-                    {page.items.map(item => (
-                      <p key={item}>Item: {item}</p>
-                    ))}
-                  </div>
-                </div>
-              ))}
-              <div>
-                <button
-                  onClick={() => fetchMore()}
-                  disabled={!canFetchMore || Boolean(isFetchingMore)}
-                >
-                  {isFetchingMore
-                    ? 'Loading more...'
-                    : canFetchMore
-                    ? 'Load More'
-                    : 'Nothing more to load'}
-                </button>
-                <button onClick={() => refetch()}>Refetch</button>
-              </div>
-              <div>
-                {isFetching && !isFetchingMore
-                  ? 'Background Updating...'
-                  : null}
-              </div>
-            </>
-          )}
-        </div>
-      )
+      states.push(state)
+
+      return null
     }
 
-    const rendered = renderWithClient(client, <Page />)
+    renderWithClient(client, <Page />)
 
-    rendered.getByText('Item: 9')
-    rendered.getByText('Page 0: 0')
+    await sleep(100)
 
-    rendered.getByText('Nothing more to load')
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({
+      data: [10],
+      hasNextPage: false,
+      isFetching: true,
+      isFetchingNextPage: false,
+      isSuccess: true,
+    })
+    expect(states[1]).toMatchObject({
+      data: [10],
+      hasNextPage: false,
+      isFetching: false,
+      isFetchingNextPage: false,
+      isSuccess: true,
+    })
   })
 
   it('should build fresh cursors on refetch', async () => {
@@ -1006,16 +989,16 @@ describe('useInfiniteQuery', () => {
         status,
         data,
         error,
-        isFetchingMore,
-        fetchMore,
-        canFetchMore,
+        isFetchingNextPage,
+        fetchNextPage,
+        hasNextPage,
         refetch,
       } = useInfiniteQuery<Result, Error>(
         key,
         (_key, nextId = 0) =>
           fetchItemsWithLimit(nextId, fetchCountRef.current++),
         {
-          getFetchMore: (lastGroup, _allGroups) => lastGroup.nextId,
+          getNextPageParam: lastPage => lastPage.nextId,
         }
       )
 
@@ -1043,12 +1026,12 @@ describe('useInfiniteQuery', () => {
               ))}
               <div>
                 <button
-                  onClick={() => fetchMore()}
-                  disabled={!canFetchMore || Boolean(isFetchingMore)}
+                  onClick={() => fetchNextPage()}
+                  disabled={!hasNextPage || Boolean(isFetchingNextPage)}
                 >
-                  {isFetchingMore
+                  {isFetchingNextPage
                     ? 'Loading more...'
-                    : canFetchMore
+                    : hasNextPage
                     ? 'Load More'
                     : 'Nothing more to load'}
                 </button>
@@ -1065,7 +1048,7 @@ describe('useInfiniteQuery', () => {
                   Remove item
                 </button>
               </div>
-              <div>{!isFetchingMore ? 'Background Updating...' : null}</div>
+              <div>{!isFetchingNextPage ? 'Background Updating...' : null}</div>
             </>
           )}
         </div>
@@ -1119,7 +1102,7 @@ describe('useInfiniteQuery', () => {
     expect(rendered.queryAllByText('Item: 4')).toHaveLength(0)
   })
 
-  it('should compute canFetchMore correctly for falsy getFetchMore return value on refetching', async () => {
+  it('should compute hasNextPage correctly for falsy getFetchMore return value on refetching', async () => {
     const key = queryKey()
     const MAX = 2
 
@@ -1133,9 +1116,9 @@ describe('useInfiniteQuery', () => {
         data,
         error,
         isFetching,
-        isFetchingMore,
-        fetchMore,
-        canFetchMore,
+        isFetchingNextPage,
+        fetchNextPage,
+        hasNextPage,
         refetch,
       } = useInfiniteQuery<Result, Error>(
         key,
@@ -1144,11 +1127,9 @@ describe('useInfiniteQuery', () => {
             nextId,
             fetchCountRef.current++,
             nextId === MAX || (nextId === MAX - 1 && isRemovedLastPage)
-              ? false
-              : undefined
           ),
         {
-          getFetchMore: (lastGroup, _allGroups) => lastGroup.nextId,
+          getNextPageParam: lastPage => lastPage.nextId,
         }
       )
 
@@ -1176,12 +1157,12 @@ describe('useInfiniteQuery', () => {
               ))}
               <div>
                 <button
-                  onClick={() => fetchMore()}
-                  disabled={!canFetchMore || Boolean(isFetchingMore)}
+                  onClick={() => fetchNextPage()}
+                  disabled={!hasNextPage || Boolean(isFetchingNextPage)}
                 >
-                  {isFetchingMore
+                  {isFetchingNextPage
                     ? 'Loading more...'
-                    : canFetchMore
+                    : hasNextPage
                     ? 'Load More'
                     : 'Nothing more to load'}
                 </button>
@@ -1191,7 +1172,7 @@ describe('useInfiniteQuery', () => {
                 </button>
               </div>
               <div>
-                {isFetching && !isFetchingMore
+                {isFetching && !isFetchingNextPage
                   ? 'Background Updating...'
                   : null}
               </div>
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index fbc6ebb60..44f36cba3 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -125,16 +125,19 @@ describe('useQuery', () => {
     await waitFor(() => rendered.getByText('Status: success'))
 
     expect(states[0]).toEqual({
-      canFetchMore: undefined,
       data: undefined,
       error: null,
       failureCount: 0,
-      fetchMore: expect.any(Function),
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isError: false,
       isFetched: false,
       isFetchedAfterMount: false,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: true,
       isPreviousData: false,
@@ -147,16 +150,19 @@ describe('useQuery', () => {
     })
 
     expect(states[1]).toEqual({
-      canFetchMore: undefined,
       data: 'test',
       error: null,
       failureCount: 0,
-      fetchMore: expect.any(Function),
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isError: false,
       isFetched: true,
       isFetchedAfterMount: true,
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: false,
       isPreviousData: false,
@@ -199,16 +205,19 @@ describe('useQuery', () => {
     await waitFor(() => rendered.getByText('Status: error'))
 
     expect(states[0]).toEqual({
-      canFetchMore: undefined,
       data: undefined,
       error: null,
       failureCount: 0,
-      fetchMore: expect.any(Function),
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isError: false,
       isFetched: false,
       isFetchedAfterMount: false,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: true,
       isPreviousData: false,
@@ -221,16 +230,19 @@ describe('useQuery', () => {
     })
 
     expect(states[1]).toEqual({
-      canFetchMore: undefined,
       data: undefined,
       error: null,
       failureCount: 1,
-      fetchMore: expect.any(Function),
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isError: false,
       isFetched: false,
       isFetchedAfterMount: false,
       isFetching: true,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: true,
       isPreviousData: false,
@@ -243,16 +255,19 @@ describe('useQuery', () => {
     })
 
     expect(states[2]).toEqual({
-      canFetchMore: undefined,
       data: undefined,
       error: 'rejected',
       failureCount: 2,
-      fetchMore: expect.any(Function),
+      fetchNextPage: expect.any(Function),
+      fetchPreviousPage: expect.any(Function),
+      hasNextPage: undefined,
+      hasPreviousPage: undefined,
       isError: true,
       isFetched: false,
       isFetchedAfterMount: false,
       isFetching: false,
-      isFetchingMore: false,
+      isFetchingNextPage: false,
+      isFetchingPreviousPage: false,
       isIdle: false,
       isLoading: false,
       isPreviousData: false,