Guide
State management in Supabase

State management in Supabase

Introduction

State management in Supabase

State management is closely related to the way you manage API layer. In this guide, we will analyze how the state is managed in Supabase.

The way we approach is we pick a route in the Supabase studio and then find the page responsible for that route (Supabase at the time of writing this article uses Page Router). We have to take a closer look at actions such as:

  1. /dashboard/projects

In this route, we are interested in finding out how the projects and organzations are rendered. Once the data is fetched, is there any state set based on this API response? or is there any other way API response is handled?

These actions are specific to Supabase, these actions vary based on an application. In cal.com, to understand how how state is managed, you would look at operations such as bookings list, event-types etc.,

[C: State management in /dashboard/projects]

At the time of writing this article, Supabase uses Pages router. We want to find out which page is responsible to load the /dashboard/projects page. Pages router uses file-based routing and the route in consideraion here ends with /projects, so we want to find a file named projects.tsx in Supabase studio

Supabase is open-source and uses a monorepo. Studio is the dashboard you would see when you visit https://supabase.com/dashboard/projects, but when you visit https://supabase.com/ www is used. Here studio, www are workspaces.

projects.tsx

You will find that projects.tsx is in studio's pages folder.

You have to ask the right questions here. What data do I see when I visit projects? There's projects and organizations data that are fetched via an API.

[Insert a screenshot of /projects route]

So how's this fetched? At line 23 , you will find this below code snippet:

const { data: organizations, isError, isSuccess } = useOrganizationsQuery()

You need to check out the Supabase's API layer to understand useOrganizationQuery shown above. In this guide, we are intrested in state management. At this point, next question would be what sort of rendering mechanism is applied here? You want to read about Rendering in Next.js Pages Router docs

Rendering strategy used in projects.tsx is client-side rendering because useOrganizationsQuery uses @tanstack/react-query found in lib/fetch/organizations.ts It is mentioned in the Client-side rendering documentation that using a data fetching library like SWR or TanStack Query to fetch data on the client is recommended and this way you implement client side rendering.

[Insert a screenshot of https://github.com/supabase/supabase/blob/master/apps/studio/pages/projects.tsx#L72-L77]

These organizations fetched are passed down as props to a component named HomePageActions.

HomePageActions

HomePageActions renders New Project button, New Organization button, Search input and the Filter button.

Why would the organizations be passed down to these actions? clicking on a New Project shows you these list of organizations.

[Insert screenshot of clicking new project to show list of organizations]

Organizations fetched are also used to show list of organizations in the Sidebar

[Insert screenshot of supabase dashboard sidebar with list of organizations]

Organizations in Sidebar

It is a common standard to place the Sidebar in the Layout. At Line 93 you will find this below code snippet:

ProjectsPage.getLayout = (page) => (
  <AccountLayout
    title="Dashboard"
    breadcrumbs={[
      {
        key: `supabase-projects`,
        label: 'Projects',
      },
    ]}
  >
    {page}
  </AccountLayout>
)

In the AccountLayout, Organizations are fetched using the query, but this time the results are from the cache, this is one of the advantages of using Tanstack query, you can also confirm this by inspecting the network call to fetch organizations, this gets called only once.

[Insert screenshot of - https://github.com/supabase/supabase/blob/master/apps/studio/components/layouts/AccountLayout/AccountLayout.tsx#L27]

AccountLayout renders the sidebar and children as shown below:

<div className="h-screen min-h-[0px] basis-0 flex-1">
  <WithSidebar
    hideSidebar={navLayoutV2}
    title={title}
    breadcrumbs={breadcrumbs}
    sections={sectionsWithHeaders}
  >
    {children}
  </WithSidebar>
</div>

sectionsWithHeaders uses this organizations array to render list of organizations

const organizationsLinks = (organizations ?? [])
    .map((organization) => ({
      isActive:
        router.pathname.startsWith('/org/') && selectedOrganization?.slug === organization.slug,
      label: organization.name,
      href: `/org/${organization.slug}/general`,
      key: organization.slug,
      icon: <PartnerIcon organization={organization} />,
    }))
    .sort((a, b) => a.label.localeCompare(b.label))