Revise guide for rrv7

This commit is contained in:
Terry Sutton committed 2025-03-03 10:47:38 -03:30
1 parent 6fb6d6ed92
commit 749e4e9362
3 files changed
+257

No files matched your search

@@ -0,0 +1,243 @@
---
title: 'Use Supabase with React Router'
subtitle: 'Learn how to create a Supabase project, add some sample data, and query from a React Router app.'
breadcrumb: 'Framework Quickstarts'
hideToc: true
---
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Create a Supabase project">
Go to [database.new](https://database.new) and create a new Supabase project.
When your project is up and running, go to the [Table Editor](https://supabase.com/dashboard/project/_/editor), create a new table and insert some data.
Alternatively, you can run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `notes` table with some sample data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create the table
create table notes (
id bigint primary key generated always as identity,
title text not null
);
-- Insert some sample data into the table
insert into notes (title)
values
('Today I created a Supabase project.'),
('I added some data and queried it from React Router.'),
('It was awesome!');
alter table notes enable row level security;
```
</StepHikeCompact.Code>
<StepHikeCompact.Details>
Make the data in your table publicly readable by adding an RLS policy:
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
create policy "public can read notes"
on public.notes
for select to anon
using (true);
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a React Router app">
Use the `create-React Router` command with our official template, to create a React Router app pre-configured with:
- [Cookie-based Auth](https://supabase.com/docs/guides/auth/server-side)
- [TypeScript](https://www.typescriptlang.org/)
- [Tailwind CSS](https://tailwindcss.com/)
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
!!!!!change this url before going live!!!!!
npx create-React Router@latest --template https://github.com/supabase-community???/React Router-quickstart
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Declare Supabase Environment Variables">
Rename `.env.example` to `.env.local` and populate with your Supabase connection variables:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```text .env.local
SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
SUPABASE_ANON_KEY=<SUBSTITUTE_SUPABASE_ANON_KEY>
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Query Supabase data from React Router">
Create a new file at `app/routes/notes.tsx` and populate with the following.
This will select all the rows from the `notes` table in Supabase and render them on the page.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<CH.Code className="min-h-[34rem]">
```ts app/routes/notes.tsx
import { useFetcher } from 'react-router'
import { createClient } from '~/lib/supabase.server'
import { useRef, useEffect } from 'react'
import type { Route } from './+types/notes'
export async function loader({ request }: Route.LoaderArgs) {
const { supabase } = createClient(request)
const { data: notes } = await supabase.from('notes').select()
return {
notes,
}
}
export async function action({ request }: Route.ActionArgs) {
const { supabase } = createClient(request)
const formData = await request.formData()
const title = formData.get('title')
if (typeof title !== 'string' || !title) {
return { error: 'Title is required' }
}
await supabase.from('notes').insert({ title })
return { success: true }
}
type ActionResponse = { success: boolean } | { error: string }
export default function Page({ loaderData }: Route.ComponentProps) {
const { notes } = loaderData
const fetcher = useFetcher<ActionResponse>()
const formRef = useRef<HTMLFormElement>(null)
// Reset form when submission is successful
useEffect(() => {
if (fetcher.state === 'idle' && !('error' in (fetcher.data || {}))) {
formRef.current?.reset()
}
}, [fetcher])
return (
<div className="flex-1 w-full flex flex-col gap-12 max-w-5xl mx-auto">
<div className="w-full max-w-5xl mx-auto mt-12">
<h1>Notes</h1>
<fetcher.Form ref={formRef} method="post" className="mb-6">
<div className="grid gap-4">
<textarea name="title" className="border rounded p-2" placeholder="Write your note..." />
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
{fetcher.state === 'submitting' ? 'Adding note...' : 'Add Note'}
</button>
</div>
</fetcher.Form>
<pre>{JSON.stringify(notes, null, 2)}</pre>
</div>
</div>
)
}
````
```ts app/lib/supabase.server.ts
import { createServerClient, parseCookieHeader, serializeCookieHeader } from "@supabase/ssr"
export function createClient(request: Request) {
const headers = new Headers()
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
)
},
},
})
return { supabase, headers }
}
````
</CH.Code>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Start the app">
Run the development server, go to http://localhost:5174/notes in a browser and you should see the list of notes.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm run dev
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
## Next steps
- Set up [Auth](/docs/guides/auth) for your app
- [Insert more data](/docs/guides/database/import-data) into your database
- Upload and serve static files using [Storage](/docs/guides/storage)
```
```
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -58 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M78.0659341,92.5875806 C90.8837956,92.5875806 101.274726,82.1966508 101.274726,69.3787894 C101.274726,56.5609279 90.8837956,46.1699982 78.0659341,46.1699982 C65.2480726,46.1699982 54.8571429,56.5609279 54.8571429,69.3787894 C54.8571429,82.1966508 65.2480726,92.5875806 78.0659341,92.5875806 Z M23.2087913,139.005163 C36.0266526,139.005163 46.4175825,128.614233 46.4175825,115.796372 C46.4175825,102.97851 36.0266526,92.5875806 23.2087913,92.5875806 C10.3909298,92.5875806 0,102.97851 0,115.796372 C0,128.614233 10.3909298,139.005163 23.2087913,139.005163 Z M232.791209,139.005163 C245.60907,139.005163 256,128.614233 256,115.796372 C256,102.97851 245.60907,92.5875806 232.791209,92.5875806 C219.973347,92.5875806 209.582418,102.97851 209.582418,115.796372 C209.582418,128.614233 219.973347,139.005163 232.791209,139.005163 Z" fill="#000000">
␍</path>
<path d="M156.565464,70.3568084 C155.823426,62.6028163 155.445577,56.1490255 149.505494,51.6131676 C141.982638,45.8687002 133.461166,49.5960243 122.964463,45.8072968 C112.650326,43.3121427 105,34.1545727 105,23.2394367 C105,10.4046502 115.577888,0 128.626373,0 C138.29063,0 146.599638,5.70747659 150.259573,13.8825477 C155.861013,24.5221258 152.220489,35.3500418 159.258242,40.8041273 C167.591489,47.2621895 178.826167,42.5329154 191.362109,48.6517412 C195.390112,50.5026944 198.799584,53.4384578 201.202056,57.0769224 C203.604528,60.7153869 205,65.0565524 205,69.7183101 C205,80.633446 197.349674,89.7910161 187.035538,92.2861702 C176.538834,96.0748977 168.017363,92.3475736 160.494506,98.092041 C152.03503,104.551712 156.563892,115.358642 149.669352,126.774447 C145.756163,134.291567 137.802119,139.43662 128.626373,139.43662 C115.577888,139.43662 105,129.03197 105,116.197184 C105,106.873668 110.581887,98.832521 118.637891,95.1306146 C131.173833,89.0117889 142.408511,93.7410629 150.741758,87.2830007 C155.549106,83.5574243 156.565464,77.8102648 156.565464,70.3568084 Z" fill="#D0021B">
␍</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,4 @@
<svg width="130" height="71" viewBox="0 0 130 71" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39.8347 47.3579C46.2945 47.3579 51.5312 42.1448 51.5312 35.7141C51.5312 29.2834 46.2945 24.0703 39.8347 24.0703C33.3749 24.0703 28.1382 29.2834 28.1382 35.7141C28.1382 42.1448 33.3749 47.3579 39.8347 47.3579ZM12.1884 70.6455C18.6482 70.6455 23.8849 65.4324 23.8849 59.0017C23.8849 52.571 18.6482 47.3579 12.1884 47.3579C5.72864 47.3579 0.491943 52.571 0.491943 59.0017C0.491943 65.4324 5.72864 70.6455 12.1884 70.6455ZM117.811 70.6455C124.271 70.6455 129.508 65.4324 129.508 59.0017C129.508 52.571 124.271 47.3579 117.811 47.3579C111.352 47.3579 106.115 52.571 106.115 59.0017C106.115 65.4324 111.352 70.6455 117.811 70.6455Z" fill="#09090B"/>
<path d="M79.396 36.2041C79.0221 32.314 78.8317 29.0761 75.838 26.8005C72.0468 23.9185 67.7522 25.7885 62.4622 23.8877C57.2642 22.6359 53.4087 18.0415 53.4087 12.5654C53.4087 6.12624 58.7396 0.90625 65.3156 0.90625C70.1861 0.90625 74.3736 3.76968 76.2181 7.8711C79.041 13.209 77.2063 18.6413 80.7531 21.3776C84.9528 24.6176 90.6147 22.2449 96.9324 25.3147C98.9624 26.2434 100.681 27.7162 101.891 29.5416C103.102 31.367 103.806 33.545 103.806 35.8838C103.806 41.3599 99.95 45.9542 94.752 47.206C89.462 49.1068 85.1674 47.2369 81.3762 50.1188C77.1129 53.3596 79.3953 58.7815 75.9206 64.5087C73.9485 68.2801 69.9399 70.8613 65.3156 70.8613C58.7396 70.8613 53.4087 65.6413 53.4087 59.2022C53.4087 54.5246 56.2218 50.4903 60.2818 48.6331C66.5995 45.5633 72.2614 47.936 76.4611 44.696C78.8838 42.8268 79.396 39.9435 79.396 36.2041Z" fill="#D0021B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB