Skip to main content
Fetch paginated or infinite scrolling data with the createInfiniteQuery primitive. It manages multiple pages of data and provides methods to load more pages in SolidJS applications.

Signature

function createInfiniteQuery<TQueryFnData, TError, TData, TQueryKey, TPageParam>(
  options: Accessor<CreateInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>>,
  queryClient?: Accessor<QueryClient>,
): CreateInfiniteQueryResult<TData, TError>

Parameters

options
Accessor<CreateInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>>
required
A Solid accessor (function) returning infinite query configuration options.
queryClient
Accessor<QueryClient>
Accessor returning a custom QueryClient instance. If not provided, uses the client from context.

Returns

CreateInfiniteQueryResult<TData, TError>
object
Reactive infinite query state and methods.

Type Parameters

  • TQueryFnData - Type of data returned by each page
  • TError - Type of error (defaults to DefaultError)
  • TData - Type of final data (defaults to InfiniteData<TQueryFnData>)
  • TQueryKey - Type of the query key (defaults to QueryKey)
  • TPageParam - Type of page parameter (defaults to unknown)

Examples

Basic Infinite Scroll

import { createInfiniteQuery } from '@tanstack/solid-query'
import { For, Show } from 'solid-js'

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: async ({ pageParam }) => {
      const res = await fetch(`/api/posts?page=${pageParam}`)
      return res.json()
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) => {
      return lastPage.hasMore ? allPages.length : undefined
    },
  }))

  return (
    <div>
      <For each={query.data?.pages}>
        {(page) => (
          <For each={page.posts}>
            {(post) => (
              <div>
                <h3>{post.title}</h3>
                <p>{post.body}</p>
              </div>
            )}
          </For>
        )}
      </For>
      
      <button
        onClick={() => query.fetchNextPage()}
        disabled={!query.hasNextPage || query.isFetchingNextPage}
      >
        <Show when={query.isFetchingNextPage} fallback="Load More">
          Loading more...
        </Show>
      </button>
    </div>
  )
}

Cursor-Based Pagination

import { createInfiniteQuery } from '@tanstack/solid-query'

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: async ({ pageParam }) => {
      const res = await fetch(`/api/posts?cursor=${pageParam}`)
      return res.json()
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }))

  return <div>{/* ... */}</div>
}

Bi-directional Pagination

import { createInfiniteQuery } from '@tanstack/solid-query'
import { Show } from 'solid-js'

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: async ({ pageParam }) => {
      const res = await fetch(`/api/posts?cursor=${pageParam}`)
      return res.json()
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage) => firstPage.prevCursor,
  }))

  return (
    <div>
      <button 
        onClick={() => query.fetchPreviousPage()}
        disabled={!query.hasPreviousPage || query.isFetchingPreviousPage}
      >
        Load Previous
      </button>
      
      {/* Posts display */}
      
      <button 
        onClick={() => query.fetchNextPage()}
        disabled={!query.hasNextPage || query.isFetchingNextPage}
      >
        Load Next
      </button>
    </div>
  )
}

With TypeScript

import { createInfiniteQuery } from '@tanstack/solid-query'

interface Post {
  id: number
  title: string
  body: string
}

interface PostsPage {
  posts: Post[]
  nextCursor?: number
  hasMore: boolean
}

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: async ({ pageParam }): Promise<PostsPage> => {
      const res = await fetch(`/api/posts?cursor=${pageParam}`)
      return res.json()
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }))

  // query.data.pages is typed as PostsPage[]

  return <div>{/* ... */}</div>
}

Reactive Query Parameters

import { createSignal } from 'solid-js'
import { createInfiniteQuery } from '@tanstack/solid-query'

function PostList() {
  const [filter, setFilter] = createSignal('all')

  const query = createInfiniteQuery(() => ({
    queryKey: ['posts', filter()],
    queryFn: async ({ pageParam }) => {
      const res = await fetch(
        `/api/posts?filter=${filter()}&cursor=${pageParam}`
      )
      return res.json()
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }))

  return (
    <div>
      <select value={filter()} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>
      {/* Posts display */}
    </div>
  )
}

Infinite Scroll with Intersection Observer

import { createInfiniteQuery } from '@tanstack/solid-query'
import { createEffect, onCleanup } from 'solid-js'

function PostList() {
  let loadMoreRef: HTMLDivElement | undefined

  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }))

  createEffect(() => {
    if (!loadMoreRef) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (
          entry.isIntersecting && 
          query.hasNextPage && 
          !query.isFetchingNextPage
        ) {
          query.fetchNextPage()
        }
      },
      { threshold: 1.0 }
    )

    observer.observe(loadMoreRef)
    onCleanup(() => observer.disconnect())
  })

  return (
    <div>
      {/* Posts display */}
      <div ref={loadMoreRef}>
        <Show when={query.isFetchingNextPage}>
          Loading...
        </Show>
      </div>
    </div>
  )
}

Refetch All Pages

import { createInfiniteQuery } from '@tanstack/solid-query'

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }))

  const refreshAll = () => {
    query.refetch() // Refetches all loaded pages
  }

  return (
    <div>
      <button onClick={refreshAll}>Refresh All</button>
      {/* Posts display */}
    </div>
  )
}

With Loading States

import { createInfiniteQuery } from '@tanstack/solid-query'
import { Show, For, Switch, Match } from 'solid-js'

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }))

  return (
    <Switch>
      <Match when={query.isLoading}>
        <div>Loading first page...</div>
      </Match>
      
      <Match when={query.isError}>
        <div>Error: {query.error?.message}</div>
      </Match>
      
      <Match when={query.isSuccess}>
        <For each={query.data.pages}>
          {(page) => (
            <For each={page.posts}>
              {(post) => <div>{post.title}</div>}
            </For>
          )}
        </For>
        
        <Show when={query.hasNextPage}>
          <button 
            onClick={() => query.fetchNextPage()}
            disabled={query.isFetchingNextPage}
          >
            {query.isFetchingNextPage ? 'Loading...' : 'Load More'}
          </button>
        </Show>
      </Match>
    </Switch>
  )
}

Page-Based Pagination

import { createInfiniteQuery } from '@tanstack/solid-query'

function PostList() {
  const query = createInfiniteQuery(() => ({
    queryKey: ['posts'],
    queryFn: async ({ pageParam }) => {
      const res = await fetch(`/api/posts?page=${pageParam}&limit=10`)
      return res.json()
    },
    initialPageParam: 1,
    getNextPageParam: (lastPage, allPages, lastPageParam) => {
      return lastPage.hasMore ? lastPageParam + 1 : undefined
    },
    getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
      return firstPageParam > 1 ? firstPageParam - 1 : undefined
    },
  }))

  return <div>{/* ... */}</div>
}

Notes

The options parameter must be an accessor (function) to integrate with SolidJS reactivity.
initialPageParam is required in v5. Make sure to provide it when creating infinite queries.
When using refetch(), all pages will be refetched. For better UX, consider using invalidateQueries to only refetch when needed.

Build docs developers (and LLMs) love