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:

  • an order clause that defines the column and direction in which to paginate
  • 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. You can pass until, to define until what value data should be loaded. If until is not defined, loadMore will always be truthy if the last page had a number of elements equal to the page size.

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 })
        .limit(1),
      { path: 'username' },
      { 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>;
}