Files
supabase/apps/ui-library/content/docs/react/infinite-query.mdx
3f205627e0 feat(library): redesign the site around the block catalog (#50372)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature — the visual redesign itself.

Part 5 of 6 in a stack that splits the library redesign into reviewable
pieces. The four PRs beneath it carry the build, content and Markdown
work; what's left here is layout, navigation and styling.

## What is the current behavior?

The library is laid out like a documentation site: a sidebar tree of
framework folders, a homepage that lists links, and a guide page that
opens with prose. That shape suits reference material, but the library's
job is to help someone find a block and install it — and the sidebar is
the only way to discover one.

## What is the new behavior?

The homepage is the catalog itself — blocks grouped by what they do
(authentication, database, storage, realtime, messaging, AI,
foundations) rather than by framework, each with a preview of what it
renders, filterable by category.

Navigation moves into a site header whose Explore menu opens the same
categories, so the catalog is reachable from any page and the per-page
sidebar tree is gone.

A guide opens with what the reader came for: the block's name, the
install command, and a preview pane with tabs — the running component
and its files — before any prose. The file tree that used to sit
mid-page under "Folder structure" is one of those tabs. Every guide also
offers a copy of the agent prompt that points at its Markdown.

Getting-started pages get the same treatment: the quickstart is now a
framework-tabbed walkthrough rather than a wall of setup links.

## Additional context

`BlockOverviewTabs` renders Preview and Files here. #50369, stacked on
top of this one, adds the third "What's added" tab — it is the only part
of the redesign that depends on the new resource analyzer, which is why
it sits above this PR rather than below it.

Also removes what the redesign orphaned: the table-of-contents component
and its `remark` / `mdast-util-toc` dependencies, and the sidebar nav
and command-item configuration the new header replaced.

The block source changes are typography only — auth card titles move
from `text-2xl` to `font-medium text-lg tracking-normal` — which is what
regenerates the auth registry artifacts.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a redesigned Supabase Library catalog with categorized blocks,
framework-aware navigation, previews, file views, and installation
actions.
* Added framework-specific quickstart guides for Next.js, React, Vue,
Nuxt, React Router, and TanStack Start.
* Added copy-to-clipboard prompts, “Open in v0” actions, starter
templates, and richer visual previews.

* **Improvements**
* Updated documentation layouts, FAQ content, typography, navigation,
accessibility, and responsive behavior.
* Improved mobile navigation, framework selection, and standardized
block installation guidance.
* Refined authentication and social-login block presentation with more
consistent heading styles.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
2026-09-29 10:45:23 +10:00

287 lines
10 KiB
Plaintext

---
title: Infinite Query Hook
description: 'React hook for infinite lists, fetching data from Supabase.'
preview: |-
<BlockOverview name="infinite-query-hook" showFiles>
<BlockPreview name="infinite-list-demo" />
</BlockOverview>
---
## Installation
<BlockItem name="infinite-query-hook" />
## Introduction
The Infinite Query Hook provides a single React hook which will make it easier to load data progressively from your Supabase database. It handles data fetching and pagination state, It is meant to be used with infinite lists or tables.
The hook is fully typed, provided you have generated and setup your database types.
## Adding types
Before using this hook, we **highly** recommend you setup database types in your project. This will make the hook fully-typesafe. More info about generating TypeScript types from database schema [here](https://supabase.com/docs/guides/api/rest/generating-types).
## Props
| Prop | Type | Description |
| --------------- | --------------------------------------------------------- | ---------------------------------------------------------------- |
| `tableName` | `string` | **Required.** The name of the Supabase table to fetch data from. |
| `columns` | `string` | Columns to select from the table. Defaults to `'*'`. |
| `pageSize` | `number` | Number of items to fetch per page. Defaults to `20`. |
| `trailingQuery` | `(query: SupabaseSelectBuilder) => SupabaseSelectBuilder` | Function to apply filters or sorting to the Supabase query. |
## Return type
data, count, isSuccess, isLoading, isFetching, error, hasMore, fetchNextPage
| Prop | Type | Description |
| --------------- | ------------- | ----------------------------------------------------------------------------------- |
| `data` | `TableData[]` | An array of fetched items. |
| `count` | `number` | Number of total items in the database. It takes `trailingQuery` into consideration. |
| `isSuccess` | `boolean` | It's true if the last API call succeeded. |
| `isLoading` | `boolean` | It's true only for the initial fetch. |
| `isFetching` | `boolean` | It's true for the initial and all incremental fetches. |
| `error` | `any` | The error from the last fetch. |
| `hasMore` | `boolean` | Whether the query has finished fetching all items from the database |
| `fetchNextPage` | `() => void` | Sends a new request for the next items |
## Type safety
The hook will use the typed defined on your Supabase client if they're setup ([more info](https://supabase.com/docs/reference/javascript/typescript-support)).
The hook also supports an custom defined result type by using `useInfiniteQuery<T>`. For example, if you have a custom type for `Product`, you can use it like this `useInfiniteQuery<Product>`.
## Usage
### With sorting
```tsx
const { data, fetchNextPage } = useInfiniteQuery({
tableName: 'products',
columns: '*',
pageSize: 10,
trailingQuery: (query) => query.order('created_at', { ascending: false }),
})
return (
<div>
{data.map((item) => (
<ProductCard key={item.id} product={item} />
))}
<Button onClick={fetchNextPage}>Load more products</Button>
</div>
)
```
### With filtering on search params
This example will filter based on a search param like `example.com/?q=hello`.
```tsx
const params = useSearchParams()
const searchQuery = params.get('q')
const { data, isLoading, isFetching, fetchNextPage, count, isSuccess } = useInfiniteQuery({
tableName: 'products',
columns: '*',
pageSize: 10,
trailingQuery: (query) => {
if (searchQuery && searchQuery.length > 0) {
query = query.ilike('name', `%${searchQuery}%`)
}
return query
},
})
return (
<div>
{data.map((item) => (
<ProductCard key={item.id} product={item} />
))}
<Button onClick={fetchNextPage}>Load more products</Button>
</div>
)
```
## Reusable components
### Infinite list (fetches as you scroll)
The following component abstracts the hook into a component. It includes few utility components for no results and end of the list.
```tsx
'use client'
import * as React from 'react'
import {
SupabaseQueryHandler,
SupabaseTableData,
SupabaseTableName,
useInfiniteQuery,
} from '@/hooks/use-infinite-query'
import { cn } from '@/lib/utils'
interface InfiniteListProps<TableName extends SupabaseTableName> {
tableName: TableName
columns?: string
pageSize?: number
trailingQuery?: SupabaseQueryHandler<TableName>
renderItem: (item: SupabaseTableData<TableName>, index: number) => React.ReactNode
className?: string
renderNoResults?: () => React.ReactNode
renderEndMessage?: () => React.ReactNode
renderSkeleton?: (count: number) => React.ReactNode
}
const DefaultNoResults = () => (
<div className="text-center text-muted-foreground py-10">No results.</div>
)
const DefaultEndMessage = () => (
<div className="text-center text-muted-foreground py-4 text-sm">You&apos;ve reached the end.</div>
)
const defaultSkeleton = (count: number) => (
<div className="flex flex-col gap-2 px-4">
{Array.from({ length: count }).map((_, index) => (
<div key={index} className="h-4 w-full bg-muted animate-pulse" />
))}
</div>
)
export function InfiniteList<TableName extends SupabaseTableName>({
tableName,
columns = '*',
pageSize = 20,
trailingQuery,
renderItem,
className,
renderNoResults = DefaultNoResults,
renderEndMessage = DefaultEndMessage,
renderSkeleton = defaultSkeleton,
}: InfiniteListProps<TableName>) {
const { data, isFetching, hasMore, fetchNextPage, isSuccess } = useInfiniteQuery({
tableName,
columns,
pageSize,
trailingQuery,
})
// Ref for the scrolling container
const scrollContainerRef = React.useRef<HTMLDivElement>(null)
// Intersection observer logic - target the last rendered *item* or a dedicated sentinel
const loadMoreSentinelRef = React.useRef<HTMLDivElement>(null)
const observer = React.useRef<IntersectionObserver | null>(null)
React.useEffect(() => {
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore && !isFetching) {
fetchNextPage()
}
},
{
root: scrollContainerRef.current, // Use the scroll container for scroll detection
threshold: 0.1, // Trigger when 10% of the target is visible
rootMargin: '0px 0px 100px 0px', // Trigger loading a bit before reaching the end
}
)
if (loadMoreSentinelRef.current) {
observer.current.observe(loadMoreSentinelRef.current)
}
return () => {
if (observer.current) observer.current.disconnect()
}
}, [isFetching, hasMore, fetchNextPage])
return (
<div ref={scrollContainerRef} className={cn('relative h-full overflow-auto', className)}>
<div>
{isSuccess && data.length === 0 && renderNoResults()}
{data.map((item, index) => renderItem(item, index))}
{isFetching && renderSkeleton && renderSkeleton(pageSize)}
<div ref={loadMoreSentinelRef} style={{ height: '1px' }} />
{!hasMore && data.length > 0 && renderEndMessage()}
</div>
</div>
)
}
```
Use the `InfiniteList` component with the [Todo List](https://supabase.com/dashboard/project/_/sql/quickstarts) quickstart.
Add `<InfiniteListDemo />` to a page to see it in action.
Ensure the [Checkbox](https://ui.shadcn.com/docs/components/checkbox) component from shadcn/ui is installed, and [regenerate/download](https://supabase.com/docs/guides/api/rest/generating-types) types after running the quickstart.
```tsx
'use client'
import { InfiniteList } from './infinite-component'
import { Checkbox } from '@/components/ui/checkbox'
import { SupabaseQueryHandler } from '@/hooks/use-infinite-query'
import { Database } from '@/lib/supabase.types'
type TodoTask = Database['public']['Tables']['todos']['Row']
// Define how each item should be rendered
const renderTodoItem = (todo: TodoTask) => {
return (
<div
key={todo.id}
className="border-b py-3 px-4 hover:bg-muted flex items-center justify-between"
>
<div className="flex items-center gap-3">
<Checkbox defaultChecked={todo.is_complete ?? false} />
<div>
<span className="font-medium text-sm text-foreground">{todo.task}</span>
<div className="text-sm text-muted-foreground">
{new Date(todo.inserted_at).toLocaleDateString()}
</div>
</div>
</div>
</div>
)
}
const orderByInsertedAt: SupabaseQueryHandler<'todos'> = (query) => {
return query.order('inserted_at', { ascending: false })
}
export const InfiniteListDemo = () => {
return (
<div className="bg-background h-[600px]">
<InfiniteList
tableName="todos"
renderItem={renderTodoItem}
pageSize={3}
trailingQuery={orderByInsertedAt}
/>
</div>
)
}
```
<Callout>
The Todo List table has Row Level Security (RLS) enabled by default. Feel free disable it
temporarily while testing. With RLS enabled, you will get an [empty
array](https://supabase.com/docs/guides/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx)
of results by default. [Read
more](https://supabase.com/docs/guides/database/postgres/row-level-security) about RLS.
</Callout>
## Further reading
- [Generating TypeScript types from the database](https://supabase.com/docs/reference/javascript/typescript-support)
- [Supabase Database API](https://supabase.com/docs/reference/javascript/select)
- [Supabase Pagination](https://supabase.com/docs/reference/javascript/select#pagination)
- [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)