Skip to Content
🎉 We are hiring! You want to work on a large-scale Supabase project? DM me →
PostgRESTQueries

Queries

The cache helpers query hooks wrap the data fetching hooks of the cache libraries and pass both the cache key and the fetcher function from on the PostgREST query. This is enabled primarily by a parser that turns any Supabase PostgREST query into a definite cache key. For example,

client .from("contact") .select("id,username,ticket_number", { count: "exact" }) .eq("username", "psteinroe");

is parsed into

postgrest$null$public$contact$select=id%2Cusername%2Cticket_number&username=eq.psteinroe$null$count=exact$head=false$

⚠️

Although you can use wildcards (*) in your query, their usage is only recommended for head: true and count: true queries. For any other query, you should be explicit about the columns you want to select. Only then is cache helpers able to do granular cache updates without refetching.

useQuery

Wrapper around the default data fetching hook that returns the query including the count without any modification of the data. The config parameter of the respective library can be passed as the second argument.

import { useQuery } from "@supabase-cache-helpers/postgrest-swr"; const client = createClient<Database>( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); function Page() { const { data, count } = useQuery( client .from("contact") .select("id,username,ticket_number", { count: "exact" }) .eq("username", "psteinroe"), { revalidateOnFocus: false, revalidateOnReconnect: false, } ); return <div>...</div>; }

useInfiniteOffsetPaginationQuery

Wrapper around the infinite hooks that transforms the data into pages and returns helper functions to paginate through them. The range filter is automatically applied based on the pageSize parameter. The respective configuration parameter can be passed as second argument.

nextPage() and previousPage() are undefined if there is no next or previous page respectively. setPage allows you to jump to a page.

The hook does not use a count query and therefore does not know how many pages there are in total. Instead, it queries one item more than the pageSize to know whether there is another page after the current one.

import { useInfiniteOffsetPaginationQuery } from '@supabase-cache-helpers/postgrest-swr'; import { createClient } from '@supabase/supabase-js'; import { Database } from './types'; const client = createClient<Database>( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); function Page() { const { currentPage, nextPage, previousPage, setPage, pages, pageIndex, isValidating, error, } = useInfiniteOffsetPaginationQuery( () => client .from('contact') .select('id,username') .order('username', { ascending: true }), { pageSize: 1, revalidateOnReconnect: true } ); return <div>...</div>; }

useOffsetInfiniteScrollQuery

Wrapper around the infinite hooks that transforms the data into a flat list and returns a loadMore function. The range filter is automatically applied based on the pageSize parameter. The SWRConfigurationInfinite can be passed as second argument.

loadMore() is undefined if there is no more data to load.

The hook does not use a count query and therefore does not know how many items there are in total. Instead, it queries one item more than the pageSize to know whether there is more data to load.

import { useOffsetInfiniteScrollQuery } from '@supabase-cache-helpers/postgrest-swr'; import { createClient } from '@supabase/supabase-js'; import { Database } from './types'; const client = createClient<Database>( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); function Page() { const { data, loadMore, isValidating, error } = useOffsetInfiniteScrollQuery( () => client .from('contact') .select('id,username') .order('username', { ascending: true }), { pageSize: 1 } ); return <div>...</div>; }

useCursorInfiniteScrollQuery

⚠️

After using this hook in production, a few subtile issues were discovered. Please use it with caution. While improving the stability, I might introduce breaking changes.

Similar to useOffsetInfiniteScrollQuery, but instead of using the offset filter to paginate, it uses a cursor. You can find a longer rationale on why this is more performant than offset-based pagination here.

For the cursor pagination to work, the query has to have:

  • at least one order clause on a column that is unique,
  • all ordered column in the select clause,
  • and a limit clause that defines page size.

loadMore() is undefined if there is no more data to load.

The hook does not use a count query and therefore does not know how many items there are in total. loadMore will always be truthy if the last page had a number of elements equal to the page size.

You need to provide CursorSettings to the hook:

export type CursorSettings< Table extends Record<string, unknown>, ColumnName extends string & keyof Table, > = { // The column to order by orderBy: ColumnName; // If the `orderBy` column is not unique, you need to provide a second, unique column. This can be the primary key. uqOrderBy?: ColumnName; };

Both columns needs to have an order clause on the query. If your primary ordering column is not unique, you need to provide a second column that is unique. This can be the primary key of the table. Otherwise, we might skip values. For an in-depth explanation, check out this blogpost.

import { useCursorInfiniteScrollQuery } from '@supabase-cache-helpers/postgrest-swr'; import { createClient } from '@supabase/supabase-js'; import { Database } from './types'; const client = createClient<Database>( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); function Page() { const { data, loadMore, isValidating, error } = useCursorInfiniteScrollQuery( () => client .from('contact') .select('id,username') .ilike('username', `${testRunPrefix}%`) .order('username', { ascending: true }) .order('id', { ascending: true }) .limit(1), { orderBy: 'username' uqOrderBy: 'id', revalidateOnFocus: false }, ); return <div>...</div>; }

useOffsetInfiniteQuery

Wrapper around the infinite hook that returns the query without any modification of the data.

import { useOffsetInfiniteQuery } from '@supabase-cache-helpers/postgrest-swr'; import { createClient } from '@supabase/supabase-js'; import { Database } from './types'; const client = createClient<Database>( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); function Page() { const { data, size, setSize, isValidating, error, mutate } = useOffsetInfiniteQuery( () => client .from('contact') .select('id,username') .order('username', { ascending: true }), { pageSize: 1 } ); return <div>...</div>; }

Using Infinite Queries with RPCs

At some point, you might start to write RPCs to optimse specific queries. In these cases, you most likely want to “push down” the pagination into the RPC. For this case, all infinite query hooks accept an rpcArgs parameter in their configs. If set, the pagination will be applied to the body of the RPC instead of the query:

import { useCursorInfiniteScrollQuery } from '@supabase-cache-helpers/postgrest-swr'; import { createClient } from '@supabase/supabase-js'; import { Database } from './types'; const client = createClient<Database>( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); function Page() { const { data, loadMore, isValidating, error } = useCursorInfiniteScrollQuery( client .rpc('contacts_cursor', { v_username_filter: `${testRunPrefix}%`, v_limit: 2, }) .select('username'), { orderBy: 'username', uqOrderBy: 'id', rpcArgs: { // the "username" cursor value will be passed as `v_username_cursor` to the RPC orderBy: 'v_username_cursor', // the "id" cursor value will be passed as `v_id_cursor` to the RPC uqOrderBy: 'v_id_cursor', }, } ); return <div>...</div>; }
Last updated on