Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Simplify remix example #399

Merged
merged 1 commit into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions examples/nextjs-server-components/utils/supabase.ts

This file was deleted.

106 changes: 3 additions & 103 deletions examples/remix/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,128 +1,28 @@
import { useEffect, useState } from 'react';
import { json, LoaderFunction, MetaFunction } from '@remix-run/node';
import { MetaFunction } from '@remix-run/node';
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useNavigate
ScrollRestoration
} from '@remix-run/react';
import { Auth, ThemeSupa } from '@supabase/auth-ui-react';
import {
createServerClient,
createBrowserClient,
SupabaseClient,
Session
} from '@supabase/auth-helpers-remix';
import { Database } from '../db_types';

export type ContextType = {
supabase: SupabaseClient<Database> | null;
session: Session | null;
};

type LoaderData = {
env: { SUPABASE_URL: string; SUPABASE_ANON_KEY: string };
initialSession: Session | null;
};

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

export const loader: LoaderFunction = async ({ request }) => {
// environment variables may be stored somewhere other than
// `process.env` in runtimes other than node
// we need to pipe these Supabase environment variables to the browser
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env;

// We can retrieve the session on the server and hand it to the client.
// This is used to make sure the session is available immediately upon rendering
const response = new Response();
const supabaseClient = createServerClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
const {
data: { session: initialSession }
} = await supabaseClient.auth.getSession();

// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{
initialSession,
env: {
SUPABASE_URL,
SUPABASE_ANON_KEY
}
},
{
headers: response.headers
}
);
};

export default function App() {
const { env, initialSession } = useLoaderData<LoaderData>();
const [supabase, setSupabase] = useState<SupabaseClient | null>(null);
const [session, setSession] = useState<Session | null>(initialSession);
const navigate = useNavigate();

const context: ContextType = { supabase, session };

useEffect(() => {
if (!supabase) {
const supabaseClient = createBrowserClient<Database>(
env.SUPABASE_URL,
env.SUPABASE_ANON_KEY
);
setSupabase(supabaseClient);
const {
data: { subscription }
} = supabaseClient.auth.onAuthStateChange((_, session) =>
setSession(session)
);
return () => {
subscription.unsubscribe();
};
}
}, []);

return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{session && (
<button
onClick={async () => {
await supabase?.auth.signOut();
navigate('/');
}}
>
Logout
</button>
)}
{supabase && !session && (
<Auth
redirectTo="http://localhost:3004"
appearance={{ theme: ThemeSupa }}
supabaseClient={supabase}
providers={['google', 'github']}
socialLayout="horizontal"
/>
)}
<hr />
<Outlet context={context} />
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
Expand Down
95 changes: 95 additions & 0 deletions examples/remix/app/routes/__supabase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { json } from '@remix-run/node';
import { Outlet, useFetcher, useLoaderData } from '@remix-run/react';
import { createBrowserClient } from '@supabase/auth-helpers-remix';
import Login from 'components/login';
import Nav from 'components/nav';
import { useEffect, useState } from 'react';
import { createServerClient } from 'utils/supabase.server';

import type { SupabaseClient, Session } from '@supabase/auth-helpers-remix';
import type { Database } from 'db_types';
import type { LoaderArgs } from '@remix-run/node';

export type TypedSupabaseClient = SupabaseClient<Database>;
export type MaybeSession = Session | null;

export type SupabaseContext = {
supabase: TypedSupabaseClient;
session: MaybeSession;
};

// this uses Pathless Layout Routes [1] to wrap up all our Supabase logic

// [1] https://remix.run/docs/en/v1/guides/routing#pathless-layout-routes

export const loader = async ({ request }: LoaderArgs) => {
// environment variables may be stored somewhere other than
// `process.env` in runtimes other than node
// we need to pipe these Supabase environment variables to the browser
const env = {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!
};

// We can retrieve the session on the server and hand it to the client.
// This is used to make sure the session is available immediately upon rendering
const response = new Response();

const supabase = createServerClient({ request, response });

const {
data: { session }
} = await supabase.auth.getSession();

// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{
env,
session
},
{
headers: response.headers
}
);
};

export default function Supabase() {
const { env, session } = useLoaderData<typeof loader>();
const fetcher = useFetcher();

// it is important to create a single instance of Supabase
// to use across client components - outlet context 👇
const [supabase] = useState(() =>
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
);

const serverAccessToken = session?.access_token;

useEffect(() => {
const {
data: { subscription }
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== serverAccessToken) {
// server and client are out of sync.
// Remix recalls active loaders after actions complete
fetcher.submit(null, {
method: 'post',
action: '/handle-supabase-auth'
});
}
});

return () => {
subscription.unsubscribe();
};
}, [serverAccessToken, supabase, fetcher]);

return (
<>
<Login supabase={supabase} session={session} />
<Nav />
<Outlet context={{ supabase, session }} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// this is used to tell Remix to call active loaders
// after a user signs in or out
export const action = () => null;
3 changes: 3 additions & 0 deletions examples/remix/app/routes/__supabase/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Index() {
return <h1>Welcome to the future!</h1>;
}
33 changes: 33 additions & 0 deletions examples/remix/app/routes/__supabase/optional-session.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { createServerClient } from 'utils/supabase.server';

import type { LoaderArgs } from '@remix-run/node';

export const loader = async ({ request }: LoaderArgs) => {
const response = new Response();
const supabase = createServerClient({ request, response });

const {
data: { session }
} = await supabase.auth.getSession();

const { data } = await supabase.from('posts').select('*');

// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, session },
{
headers: response.headers
}
);
};

export default function OptionalSession() {
// by fetching the session in the loader, we ensure it is available
// for first SSR render - no flashing of incorrect state
const { data, session } = useLoaderData<typeof loader>();

return <pre>{JSON.stringify({ data, session }, null, 2)}</pre>;
}
46 changes: 46 additions & 0 deletions examples/remix/app/routes/__supabase/realtime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Form, useLoaderData } from '@remix-run/react';
import { createServerClient } from 'utils/supabase.server';
import { json } from '@remix-run/node';
import RealtimePosts from 'components/realtime-posts';

import type { ActionArgs, LoaderArgs } from '@remix-run/node';

export const action = async ({ request }: ActionArgs) => {
const response = new Response();
const supabase = createServerClient({ request, response });

const { post } = Object.fromEntries(await request.formData());

const { error } = await supabase
.from('posts')
.insert({ content: String(post) });

if (error) {
console.log(error);
}

return json(null, { headers: response.headers });
};

export const loader = async ({ request }: LoaderArgs) => {
const response = new Response();
const supabase = createServerClient({ request, response });

const { data } = await supabase.from('posts').select();

return json({ posts: data ?? [] }, { headers: response.headers });
};

export default function Index() {
const { posts } = useLoaderData<typeof loader>();

return (
<>
<RealtimePosts serverPosts={posts} />
<Form method="post">
<input type="text" name="post" />
<button type="submit">Send</button>
</Form>
</>
);
}
43 changes: 43 additions & 0 deletions examples/remix/app/routes/__supabase/required-session.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { json, redirect } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { createServerClient } from 'utils/supabase.server';

import type { LoaderArgs } from '@remix-run/node';

export const loader = async ({ request }: LoaderArgs) => {
const response = new Response();
const supabase = createServerClient({ request, response });

const {
data: { session }
} = await supabase.auth.getSession();

if (!session) {
// there is no session, therefore, we are redirecting
// to the landing page. The `/?index` is required here
// for Remix to correctly call our loaders
return redirect('/?index', {
// we still need to return response.headers to attach the set-cookie header
headers: response.headers
});
}

const { data } = await supabase.from('posts').select('*');

// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, session },
{
headers: response.headers
}
);
};

export default function RequiredSession() {
// by fetching the session in the loader, we ensure it is available
// for first SSR render - no flashing of incorrect state
const { data, session } = useLoaderData<typeof loader>();

return <pre>{JSON.stringify({ data, session }, null, 2)}</pre>;
}
32 changes: 0 additions & 32 deletions examples/remix/app/routes/client-side-fetching.tsx

This file was deleted.

Loading