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>;
}