New Task Page UI

Introduction: Building a User-Friendly Task Creation Page

Welcome back! In the last lessons, you learned how to build reusable forms with validation and how to give users instant feedback using toast notifications and an API client. Now, you are ready to put these skills together to create a new page where users can add tasks to your app.

In this lesson, you will build a "New Task" page that not only lets users submit new tasks but also gives them clear feedback if something goes wrong or if the app is busy processing their request. You will also see how to show loading indicators and handle errors in a way that keeps your app looking professional and easy to use.

By the end of this lesson, you will know how to:

  • Build a page for creating new tasks.
  • Show users when the app is loading or if an error occurs.
  • Give feedback using toast notifications and global UI states.

Let’s get started!

Quick Recap: App Structure and Key Tools

Before we dive in, let’s quickly remind ourselves of the tools and structure you already have in place:

  • Reusable Task Form: You have a form component that handles user input and validation.
  • Toast Notifications: You can show quick messages to users for success or error events.
  • API Client: You have a simple way to send requests to your backend and handle responses.

All of these are already set up in your project, so you can focus on building the new page and connecting everything together.

Implementing the New Task Page

Let’s look at how to build the page where users can create a new task. Here is the main code for the NewTaskPage component:

'use client';
import { useRouter } from 'next/navigation';
import { TaskForm, TaskFormValues } from '@/components/tasks/TaskForm';
import { api } from '@/lib/apiClient';
import { useToast } from '@/components/ui/Toast';

export default function NewTaskPage() {
  const router = useRouter();
  const toast = useToast();

  const onSubmit = async (values: TaskFormValues) => {
    const res = await api.post('/api/tasks', values);
    if ('error' in res) {
      toast.error('Failed to create task');
      return;
    }
    toast.success('Task created');
    router.push('/tasks');
  };

  return (
    <div className="space-y-6">
      <h1 className="text-2xl font-semibold">Create Task</h1>
      <div className="rounded-lg border bg-white p-4">
        <TaskForm onSubmit={onSubmit} submitLabel="Create" />
      </div>
    </div>
  );
}

Let’s break down what’s happening here:

  • The component imports the useRouter hook from Next.js, which lets you navigate to different pages.
  • It also imports the TaskForm component, which you built earlier, and the api client for making requests.
  • The useToast hook is used to show feedback messages to the user.

The key part is the onSubmit function:

  • When the user submits the form, onSubmit sends the form data to the /api/tasks endpoint using the API client.

  • If there is an error in the response, it shows an error toast:
    Output:

    Failed to create task
  • If the request is successful, it shows a success toast and redirects the user to the tasks list:
    Output:

    Task created

    Then, the user is taken to the /tasks page.

The TaskForm is rendered with the onSubmit handler and a submit button labeled "Create".

This setup makes sure users always know what’s happening — whether their task was created or if something went wrong.

Adding Global Loading and Error UI

Sometimes, your app needs to show users that something is happening in the background (like loading data) or that an error has occurred. Next.js makes this easy with special files: loading.tsx and error.tsx.

Here’s how your app handles these states:

Loading State:

// src/app/(dashboard)/loading.tsx
export default function Loading() {
  return (
    <div className="flex items-center justify-center py-16 text-gray-500">
      Global Loading… (from loading.tsx)
    </div>
  );
}

When a page is loading, this message appears in the center of the screen:

Output:

Global Loading… (from loading.tsx)

Error State:

// src/app/(dashboard)/error.tsx
'use client';

export default function Error({ error }: { error: Error }) {
  return (
    <div className="rounded border border-red-200 bg-red-50 p-4 text-red-800">
      <div className="font-semibold">Global Error Boundary</div>
      <div className="text-sm">{error.message}</div>
    </div>
  );
}

If something goes wrong, users see a clear error message:

Output:

Global Error Boundary
[Error message]

Testing Loading and Error States:

loading.tsx is used by Next.js for route-segment loading/Suspense during navigation or async rendering. The mock page at /mock/loading-error uses local client state to simulate a loading message after clicking "Trigger Success"; clicking "Trigger Failure" throws during render so you can test the route error boundary.

This approach keeps your app user-friendly, even when things don’t go as planned.

Summary And Practice Preview

In this lesson, you learned how to build a new task page that uses your existing form, API client, and toast notifications to give users clear feedback. You also saw how to handle loading and error states globally, so users always know what’s happening in your app.

Next, you’ll get a chance to practice these skills by creating and testing your own task pages and handling different UI states. This hands-on work will help you become more confident in building user-friendly features in your Next.js projects.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal