Proper data flow in Supabase Next app
Answered
Masai Lion posted this in #help-forum
Masai LionOP
I'm trying to create a page in my Next app that was created from a Supabase boilerplate that allows a user to get a list of rows and then update one of those rows. I ran into a problem when I tried to create an update function tied to a button onClick where it gave me an error because the page is a server component
From there, I tried to split up code into server and client components, but ran into problems where I still can't run server code (Supabase server client which stores the user session) in the client component. At one point I thought I figured it out by passing a function with use server at the top as a prop to the client component, but when I clicked the button, it didn't do anything.
What am I missing? It's been a while since I've worked in React, but I still understand it's concepts. I'm new to Next, but if this is how it works, I'll probably abandon it pretty quickly as it shouldn't be this difficult for me to pull data from the server and then update it
From there, I tried to split up code into server and client components, but ran into problems where I still can't run server code (Supabase server client which stores the user session) in the client component. At one point I thought I figured it out by passing a function with use server at the top as a prop to the client component, but when I clicked the button, it didn't do anything.
What am I missing? It's been a while since I've worked in React, but I still understand it's concepts. I'm new to Next, but if this is how it works, I'll probably abandon it pretty quickly as it shouldn't be this difficult for me to pull data from the server and then update it
Answered by Plague
You need to move the Server Action to a separate file and import it directly into the client component and call it that way.
See this example: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#event-handlers
See this example: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#event-handlers
15 Replies
Masai LionOP
Here is what I have right now. It doesn't error at all, but the approveFile function also doesn't run at all. I get a console.log statement from the onClick function, but nothing from the inner logging of the approveFile function
// page.tsx
import { createClient } from '@/utils/supabase/server';
import { File, FileTable } from './file';
async function approveFile(file: File) {
'use server';
const supabase = createClient();
console.log("Approve file", file);
await supabase.functions.invoke("approve-file", {
body: JSON.stringify(file),
})
}
export default async function PredictedReceipts() {
const supabase = createClient();
const { data: files, error } = await supabase.from('files').select('*').returns<File[]>();
if (error) {
console.error(error);
return <div>Error: {error.message}</div>;
}
return (
<>
<h1 className="text-2xl font-medium text-center">Title</h1>
<FileTable files={files} approveAction={approveFile} />
</>
);
}// file.tsx
'use client';
import { Button } from '@/components/ui/button';
import { Image } from 'lucide-react';
export interface File {
id: string;
name: string;
mime_type: string;
}
export function FileTable(props: Readonly<{
files: File[];
approveAction: (file: File) => Promise<void>;
}>) {
return (
<table className="table-auto table border-collapse">
<thead className="table-header-group">
<tr className="table-row">
<th className="table-cell text-center border-b px-3">Link</th>
<th className="table-cell text-center border-b px-3">Name</th>
<th className="table-cell text-center border-b px-3"></th>
</tr>
</thead>
<tbody className="table-row-group">
{props.files?.map((file) => (
<tr key={file.google_drive_id} className="table-row">
<td className="table-cell text-center border-b px-3">
<a href={`https://drive.google.com/file/d/${file.id}/view`} target="_blank">
<Image />
</a>
</td>
<td className="table-cell text-center border-b px-3">{file.name}</td>
<td className="table-cell text-center border-b px-3 py-1">
<Button
size={'sm'}
onClick={async() => {
console.log("ReceiptsTable onClick handler");
await props.approveAction(file);
}}
>
Approve
</Button>
</td>
</tr>
))}
</tbody>
</table>
);
}You need to move the Server Action to a separate file and import it directly into the client component and call it that way.
See this example: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#event-handlers
See this example: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#event-handlers
Answer
Masai LionOP
I moved it to an action and imported it like in the docs you linked. Now I get an error:
Error:
× You're importing a component that needs next/headers. That only works in a Server Component which is not supported in the pages/ directory@Masai Lion I moved it to an action and imported it like in the docs you linked. Now I get an error:
Error:
× You're importing a component that needs next/headers. That only works in a Server Component which is not supported in the pages/ directory
Show me how you did it, it shouldn't be a component it should be a regular function
Masai LionOP
// file.tsx (using imported action, truncated)
'use client';
import { moveReceipt } from '@/app/actions/receipt';
...
export function ReceiptsTable(props: Readonly<{
files: File[];
}>) {
return (
...
<Button
size={'sm'}
onClick={async() => {
console.log("ReceiptsTable onClick handler");
await moveReceipt(file);
}}
>
Approve
</Button>
...
}// actions/receipt.ts
import { createClient } from "@/utils/supabase/server";
import { File } from "../user/receipts/receipts";
export async function moveReceipt(file: File) {
const supabase = createClient();
console.log("Move receipt", file);
// await supabase.functions.invoke("move-receipt", {
// body: JSON.stringify(fileId),
// });
}did you write
"use server" at the top of the actions file?it needs that
Other than that, this looks fine. If you add
"use server" it should work as expected.Masai LionOP
Yeah, you were right that I needed to add that. Now I don't get an error. But the imported function doesn't do anything. I expect to see at least the console.log statement, but I only see the one in the client page
@Masai Lion Yeah, you were right that I needed to add that. Now I don't get an error. But the imported function doesn't do anything. I expect to see at least the console.log statement, but I only see the one in the client page
It runs on the server, check your terminal that's where it will be.
Masai LionOP
Holy crap!.. Wow!!.. and there is the missing piece that I needed to know. THANK YOU!!
No problem