Skip to content

Commit cfc18db

Browse files
committed
simplify remix example
1 parent 9a0fb93 commit cfc18db

24 files changed

+662
-922
lines changed

examples/nextjs-server-components/utils/supabase.ts

-4
This file was deleted.

examples/remix/app/root.tsx

+3-103
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,28 @@
1-
import { useEffect, useState } from 'react';
2-
import { json, LoaderFunction, MetaFunction } from '@remix-run/node';
1+
import { MetaFunction } from '@remix-run/node';
32
import {
43
Links,
54
LiveReload,
65
Meta,
76
Outlet,
87
Scripts,
9-
ScrollRestoration,
10-
useLoaderData,
11-
useNavigate
8+
ScrollRestoration
129
} from '@remix-run/react';
13-
import { Auth, ThemeSupa } from '@supabase/auth-ui-react';
14-
import {
15-
createServerClient,
16-
createBrowserClient,
17-
SupabaseClient,
18-
Session
19-
} from '@supabase/auth-helpers-remix';
20-
import { Database } from '../db_types';
21-
22-
export type ContextType = {
23-
supabase: SupabaseClient<Database> | null;
24-
session: Session | null;
25-
};
26-
27-
type LoaderData = {
28-
env: { SUPABASE_URL: string; SUPABASE_ANON_KEY: string };
29-
initialSession: Session | null;
30-
};
3110

3211
export const meta: MetaFunction = () => ({
3312
charset: 'utf-8',
3413
title: 'New Remix App',
3514
viewport: 'width=device-width,initial-scale=1'
3615
});
3716

38-
export const loader: LoaderFunction = async ({ request }) => {
39-
// environment variables may be stored somewhere other than
40-
// `process.env` in runtimes other than node
41-
// we need to pipe these Supabase environment variables to the browser
42-
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env;
43-
44-
// We can retrieve the session on the server and hand it to the client.
45-
// This is used to make sure the session is available immediately upon rendering
46-
const response = new Response();
47-
const supabaseClient = createServerClient<Database>(
48-
process.env.SUPABASE_URL!,
49-
process.env.SUPABASE_ANON_KEY!,
50-
{ request, response }
51-
);
52-
const {
53-
data: { session: initialSession }
54-
} = await supabaseClient.auth.getSession();
55-
56-
// in order for the set-cookie header to be set,
57-
// headers must be returned as part of the loader response
58-
return json(
59-
{
60-
initialSession,
61-
env: {
62-
SUPABASE_URL,
63-
SUPABASE_ANON_KEY
64-
}
65-
},
66-
{
67-
headers: response.headers
68-
}
69-
);
70-
};
71-
7217
export default function App() {
73-
const { env, initialSession } = useLoaderData<LoaderData>();
74-
const [supabase, setSupabase] = useState<SupabaseClient | null>(null);
75-
const [session, setSession] = useState<Session | null>(initialSession);
76-
const navigate = useNavigate();
77-
78-
const context: ContextType = { supabase, session };
79-
80-
useEffect(() => {
81-
if (!supabase) {
82-
const supabaseClient = createBrowserClient<Database>(
83-
env.SUPABASE_URL,
84-
env.SUPABASE_ANON_KEY
85-
);
86-
setSupabase(supabaseClient);
87-
const {
88-
data: { subscription }
89-
} = supabaseClient.auth.onAuthStateChange((_, session) =>
90-
setSession(session)
91-
);
92-
return () => {
93-
subscription.unsubscribe();
94-
};
95-
}
96-
}, []);
97-
9818
return (
9919
<html lang="en">
10020
<head>
10121
<Meta />
10222
<Links />
10323
</head>
10424
<body>
105-
{session && (
106-
<button
107-
onClick={async () => {
108-
await supabase?.auth.signOut();
109-
navigate('/');
110-
}}
111-
>
112-
Logout
113-
</button>
114-
)}
115-
{supabase && !session && (
116-
<Auth
117-
redirectTo="http://localhost:3004"
118-
appearance={{ theme: ThemeSupa }}
119-
supabaseClient={supabase}
120-
providers={['google', 'github']}
121-
socialLayout="horizontal"
122-
/>
123-
)}
124-
<hr />
125-
<Outlet context={context} />
25+
<Outlet />
12626
<ScrollRestoration />
12727
<Scripts />
12828
<LiveReload />
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { json } from '@remix-run/node';
2+
import { Outlet, useFetcher, useLoaderData } from '@remix-run/react';
3+
import { createBrowserClient } from '@supabase/auth-helpers-remix';
4+
import Login from 'components/login';
5+
import Nav from 'components/nav';
6+
import { useEffect, useState } from 'react';
7+
import { createServerClient } from 'utils/supabase.server';
8+
9+
import type { SupabaseClient, Session } from '@supabase/auth-helpers-remix';
10+
import type { Database } from 'db_types';
11+
import type { LoaderArgs } from '@remix-run/node';
12+
13+
export type TypedSupabaseClient = SupabaseClient<Database>;
14+
export type MaybeSession = Session | null;
15+
16+
export type SupabaseContext = {
17+
supabase: TypedSupabaseClient;
18+
session: MaybeSession;
19+
};
20+
21+
// this uses Pathless Layout Routes [1] to wrap up all our Supabase logic
22+
23+
// [1] https://remix.run/docs/en/v1/guides/routing#pathless-layout-routes
24+
25+
export const loader = async ({ request }: LoaderArgs) => {
26+
// environment variables may be stored somewhere other than
27+
// `process.env` in runtimes other than node
28+
// we need to pipe these Supabase environment variables to the browser
29+
const env = {
30+
SUPABASE_URL: process.env.SUPABASE_URL!,
31+
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!
32+
};
33+
34+
// We can retrieve the session on the server and hand it to the client.
35+
// This is used to make sure the session is available immediately upon rendering
36+
const response = new Response();
37+
38+
const supabase = createServerClient({ request, response });
39+
40+
const {
41+
data: { session }
42+
} = await supabase.auth.getSession();
43+
44+
// in order for the set-cookie header to be set,
45+
// headers must be returned as part of the loader response
46+
return json(
47+
{
48+
env,
49+
session
50+
},
51+
{
52+
headers: response.headers
53+
}
54+
);
55+
};
56+
57+
export default function Supabase() {
58+
const { env, session } = useLoaderData<typeof loader>();
59+
const fetcher = useFetcher();
60+
61+
// it is important to create a single instance of Supabase
62+
// to use across client components - outlet context 👇
63+
const [supabase] = useState(() =>
64+
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
65+
);
66+
67+
const serverAccessToken = session?.access_token;
68+
69+
useEffect(() => {
70+
const {
71+
data: { subscription }
72+
} = supabase.auth.onAuthStateChange((event, session) => {
73+
if (session?.access_token !== serverAccessToken) {
74+
// server and client are out of sync.
75+
// Remix recalls active loaders after actions complete
76+
fetcher.submit(null, {
77+
method: 'post',
78+
action: '/handle-supabase-auth'
79+
});
80+
}
81+
});
82+
83+
return () => {
84+
subscription.unsubscribe();
85+
};
86+
}, [serverAccessToken, supabase, fetcher]);
87+
88+
return (
89+
<>
90+
<Login supabase={supabase} session={session} />
91+
<Nav />
92+
<Outlet context={{ supabase, session }} />
93+
</>
94+
);
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { json, redirect } from '@remix-run/node';
2+
import { useLoaderData } from '@remix-run/react';
3+
import { createServerClient } from 'utils/supabase.server';
4+
5+
import type { LoaderArgs } from '@remix-run/node';
6+
7+
export const loader = async ({ request }: LoaderArgs) => {
8+
const response = new Response();
9+
10+
const supabaseClient = createServerClient({ request, response });
11+
12+
const {
13+
data: { session }
14+
} = await supabaseClient.auth.getSession();
15+
16+
if (!session) {
17+
// there is no session, therefore, we are redirecting
18+
// to the landing page. The `/?index` is required here
19+
// for Remix to correctly call our loaders
20+
return redirect('/?index', {
21+
// we still need to return response.headers to attach the set-cookie header
22+
headers: response.headers
23+
});
24+
}
25+
26+
// Retrieve provider_token & logged in user's third-party id from metadata
27+
const { provider_token, user } = session;
28+
const userId = user.user_metadata.user_name;
29+
30+
const allRepos = await (
31+
await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
32+
method: 'GET',
33+
headers: {
34+
Authorization: `Bearer: ${provider_token}`
35+
}
36+
})
37+
).json();
38+
39+
// in order for the set-cookie header to be set,
40+
// headers must be returned as part of the loader response
41+
return json(
42+
{ user, allRepos },
43+
{
44+
headers: response.headers
45+
}
46+
);
47+
};
48+
49+
export default function GitHubProviderToken() {
50+
// by fetching the user in the loader, we ensure it is available
51+
// for first SSR render - no flashing of incorrect state
52+
const { user, allRepos } = useLoaderData<typeof loader>();
53+
54+
return <pre>{JSON.stringify({ user, allRepos }, null, 2)}</pre>;
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// this is used to tell Remix to call active loaders
2+
// after a user signs in or out
3+
export const action = () => null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Index() {
2+
return <h1>Welcome to the future!</h1>;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { json } from '@remix-run/node';
2+
import { useLoaderData } from '@remix-run/react';
3+
import { createServerClient } from 'utils/supabase.server';
4+
5+
import type { LoaderArgs } from '@remix-run/node';
6+
7+
export const loader = async ({ request }: LoaderArgs) => {
8+
const response = new Response();
9+
const supabase = createServerClient({ request, response });
10+
11+
const {
12+
data: { session }
13+
} = await supabase.auth.getSession();
14+
15+
const { data } = await supabase.from('posts').select('*');
16+
17+
// in order for the set-cookie header to be set,
18+
// headers must be returned as part of the loader response
19+
return json(
20+
{ data, session },
21+
{
22+
headers: response.headers
23+
}
24+
);
25+
};
26+
27+
export default function OptionalSession() {
28+
// by fetching the session in the loader, we ensure it is available
29+
// for first SSR render - no flashing of incorrect state
30+
const { data, session } = useLoaderData<typeof loader>();
31+
32+
return <pre>{JSON.stringify({ data, session }, null, 2)}</pre>;
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Form, useLoaderData } from '@remix-run/react';
2+
import { createServerClient } from 'utils/supabase.server';
3+
import { json } from '@remix-run/node';
4+
import RealtimePosts from 'components/realtime-posts';
5+
6+
import type { ActionArgs, LoaderArgs } from '@remix-run/node';
7+
8+
export const action = async ({ request }: ActionArgs) => {
9+
const response = new Response();
10+
const supabase = createServerClient({ request, response });
11+
12+
const { post } = Object.fromEntries(await request.formData());
13+
14+
const { error } = await supabase
15+
.from('posts')
16+
.insert({ content: String(post) });
17+
18+
if (error) {
19+
console.log(error);
20+
}
21+
22+
return json(null, { headers: response.headers });
23+
};
24+
25+
export const loader = async ({ request }: LoaderArgs) => {
26+
const response = new Response();
27+
const supabase = createServerClient({ request, response });
28+
29+
const { data } = await supabase.from('posts').select();
30+
31+
return json({ posts: data ?? [] }, { headers: response.headers });
32+
};
33+
34+
export default function Index() {
35+
const { posts } = useLoaderData<typeof loader>();
36+
37+
return (
38+
<>
39+
<RealtimePosts serverPosts={posts} />
40+
<Form method="post">
41+
<input type="text" name="post" />
42+
<button type="submit">Send</button>
43+
</Form>
44+
</>
45+
);
46+
}

0 commit comments

Comments
 (0)