When fetching data from the Supabase Postgres Database, we use the Supabase JS client.
To make data-fetching from Supabase more reusable, our convention is to create a custom React hook for each query we make.
For example, let's take a look at the hook to fetch an organization from Supabase.
First, we want to write a query that receives two parameters:
- a Supabase client interface
- an organization ID
export function getOrganizationById(
client: Client,
organizationId: number
) {
return client
.from('organizations')
.select(`
id,
name,
logoURL: logo_url
`)
.eq('id', organizationId)
.throwOnError()
.single();
}
It's important that we pass the client as a parameter so that we can use the function in both the browser and the server.
Now, if we want to use a React Hook to fetch the organization from one of
our components, we can use the useQuery
hook from react-query
:
import useSupabase from '~/core/hooks/use-supabase';
import { getOrganizationById } from '~/lib/organizations/database/queries';
import { useQuery } from '@tanstack/react-query';
function useOrganizationQuery(
organizationId: number
) {
const client = useSupabase();
const key = ['organization', organizationId];
return useQuery(key, async () => {
return getOrganizationById(client, organizationId).then(
(result) => result.data
);
});
}
export default useOrganizationQuery;
Retrieving data using a React hook
Now that we have a hook to fetch an organization, we can use it in our components to retrieve the data.
import { useOrganizationQuery } from './use-organization-query';
function OrganizationCard({ organizationId }) {
const {
data: organization,
isLoading,
error
} = useOrganizationQuery(organizationId);
/* data is loading */
if (isLoading) {
return <div>Loading...</div>
}
/* request errored */
if (error) {
return <div>Error!</div>
}
/* request successful, we can access "organization" */
return <div>{organization.name}</div>;
}
Retrieving data from the server
When we want to fetch data from the server, we can import a server Supabase client and use it to fetch data.
import {
throwInternalServerErrorException,
} from '~/core/http-exceptions';
import { json } from '@remix-run/node';
import getSupabaseServerClient from '~/core/supabase/server-client';
import { parseOrganizationIdCookie } from '~/lib/server/cookies/organization.cookie';
import { getOrganizationById } from "~/lib/tasks/queries";
export async function loader({ request }: LoaderArgs) {
const client = getSupabaseServerClient(request);
const organizationId = Number(await parseOrganizationIdCookie(request));
try {
const { data } = await getOrganizationById(client, organizationId);
return json(data);
} catch (error) {
return throwInternalServerErrorException();
}
}
When you want to ensure your data is rendered on the server, you should ensure you're loading the data using the loader
function. Then, you can co-ordinate data re-fetching on the client when the data changes using useSubmit
or useFetcher
.
When the data is only fetched on the client, you can use useQuery
to fetch the data and re-fetch the stale queries.