• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License
  • Global Configuration
    • Environment Variables
    • Feature Flags
  • Server Actions
    • Sending CSRF Token to Actions
    • Server Actions Error Handling
  • The Makerkit SDK
    • User SDK
    • Organization SDK
    • Organization Subscription SDK
    • Data Loader SDK
  • Architecture and Folder Structure
    • Structure your Application
    • Data Model
    • Introduction
    • Initial Setup
    • Running the App
    • Project Configuration
    • Environment Variables
    • Tailwind CSS and Styling
    • Authentication
    • Onboarding Flow
    • Database Schema
    • Supabase: Data Fetching
    • Supabase: Data Writing
    • Routing
    • Building the Tasks page
    • Building the Task Detail page
    • API Routes
    • Application Pages
    • API Routes Validation
    • Translations
    • Functions you need to know
    • Adding pages to the Marketing Site
    • Deploying to Production
    • Updating to the latest version
This documentation is for a legacy version of Next.js and Supabase. For the latest version, please visit the Next.js and Supabase V2 documentation

Building the Task Detail page | Next.js Supabase

In this tutorial, we will build the Task Detail page of the Tasks App.

The Task Detail page is the page that shows the details of a specific task. It is a child page of the Tasks page.

As you may know, we can define a dynamic path in Next.js by using square brackets. For example, we can define a dynamic path for the Tasks page by creating a file called app/dashboard/[organization]/tasks/[task]/page.tsx. This will create a dynamic path for the Tasks page that will be available at /dashboard/:organization/tasks/:task.

Building the Task Detail Container

Before we build the page, we define a client component named TaskItemContainer that will be used by the page. This component will be responsible for rendering the form that will be used to update the task.

app/dashboard/[organization]/tasks/components/TaskItemContainer.tsx
'use client';
import { FormEventHandler, useCallback, useTransition } from 'react';
import { ChevronLeftIcon } from '@heroicons/react/24/outline';
import Textarea from '~/core/ui/Textarea';
import Label from '~/core/ui/Label';
import Button from '~/core/ui/Button';
import Heading from '~/core/ui/Heading';
import { TextFieldInput, TextFieldLabel } from '~/core/ui/TextField';
import type Task from '~/lib/tasks/types/task';
import { updateTaskAction } from '~/lib/tasks/actions';
const TaskItemContainer: React.FC<{
task: Task;
}> = ({ task }) => {
const [isMutating, startTransition] = useTransition();
const onUpdate: FormEventHandler<HTMLFormElement> = useCallback(
(e) => {
e.preventDefault();
const data = new FormData(e.currentTarget);
const name = data.get('name') as string;
const description = data.get('description') as string;
startTransition(async () => {
await updateTaskAction({
task: {
name,
description,
id: task.id,
},
csrfToken,
});
});
},
[csrfToken, task.id],
);
return (
<form onSubmit={onUpdate}>
<div className={'flex flex-col space-y-4 max-w-xl'}>
<Heading type={2}>{task.name}</Heading>
<TextFieldLabel>
Name
<TextFieldInput required defaultValue={task.name} name={'name'} />
</TextFieldLabel>
<Label>
Description
<Textarea
className={'h-32'}
name={'description'}
defaultValue={task.description || ''}
/>
</Label>
<div className={'flex space-x-2 justify-between'}>
<Button href={'../tasks'} color={'transparent'}>
<span className={'flex space-x-2 items-center'}>
<ChevronLeftIcon className={'w-4'} />
<span>Back to Tasks</span>
</span>
</Button>
<Button loading={isMutating}>Update Task</Button>
</div>
</div>
</form>
);
};
export default TaskItemContainer;

Building the Task Detail page

Now that we have the TaskItemContainer component, we can build the Task Detail page. The page will be responsible for fetching the task data and passing it to the TaskItemContainer component.

First, we define the function loadTaskData - responsible for fetching the task data - and then we use it in the TaskPage component.

If the task does not exist, we redirect the user back to the dashboard page. This is done by using the redirect function from the next/navigation module.

app/dashboard/[organization]/tasks/[task]/page.tsx
async function loadTaskData(taskId: string) {
const client = getSupabaseServerComponentClient();
const { data: task } = await getTask(client, Number(taskId));
if (!task) {
redirect('/dashboard');
}
return {
task,
};
}

Below is the full code for the Task Detail page.

app/dashboard/[organization]/tasks/[task]/page.tsx
import React, { use } from 'react';
import { redirect } from 'next/navigation';
import ArrowLeftIcon from '@heroicons/react/24/outline/ArrowLeftIcon';
import Button from '~/core/ui/Button';
import getSupabaseServerComponentClient from '~/core/supabase/server-component-client';
import { getTask } from '~/lib/tasks/queries';
import AppHeader from '~/app/dashboard/[organization]/components/AppHeader';
import AppContainer from '~/app/dashboard/[organization]/components/AppContainer';
import TaskItemContainer from '~/app/dashboard/[organization]/tasks/components/TaskItemContainer';
import { withI18n } from '~/i18n/with-i18n';
interface Context {
params: {
task: string;
};
}
export const metadata = {
title: `Task`,
};
const TaskPage = ({ params }: Context) => {
const data = use(loadTaskData(params.task));
const task = data.task;
return (
<>
<AppHeader>
<TaskPageHeading />
</AppHeader>
<AppContainer>
<TaskItemContainer task={task} />
</AppContainer>
</>
);
};
function TaskPageHeading() {
return (
<div className={'flex items-center space-x-6'}>
<span>Task</span>
<Button size={'small'} color={'transparent'} href={'../tasks'}>
<ArrowLeftIcon className={'mr-2 h-4'} />
Back to Tasks
</Button>
</div>
);
}
async function loadTaskData(taskId: string) {
const client = getSupabaseServerComponentClient();
const { data: task } = await getTask(client, Number(taskId));
if (!task) {
redirect('/dashboard');
}
return {
task,
};
}
export default withI18n(TaskPage);

Perfect, our Tasks App is now complete! 🎉

In the next steps, we take a look at some things you should now while keeping working on your app.

On this page
  1. Building the Task Detail Container
    1. Building the Task Detail page