Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Form Status State Hooks
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

useFormStatus and useFormState Hooks

Managing form loading states and form-level state

React provides two powerful hooks for forms: useFormStatus for accessing submission status (loading states, pending data) and useFormState for managing form-level state that updates with Server Action results. Together, they enable responsive forms with instant feedback, loading indicators, and progressive enhancement. Let's build forms that feel fast and provide excellent UX!

useFormStatus Hook

What is useFormStatus?

useFormStatus is a React hook that provides status information about the parent <form> submission:

useFormStatus Returns:

  • pending: Boolean - true during form submission
  • data: FormData being submitted (or null)
  • method: HTTP method ('get' or 'post')
  • action: Function reference or URL

⚠️ Critical: useFormStatus Must Be in Child Component

useFormStatus must be called from a component that is rendered inside the <form> element. It won't work in the same component that renders the form.

Basic Submit Button with Loading State

app/components/SubmitButton.tsx
'use client';

import { useFormStatus } from 'react-dom';

export function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className="px-6 py-3 bg-blue-600 text-white rounded-lg disabled:bg-gray-400 disabled:cursor-not-allowed"
    >
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

// ✅ pending is true during submission
// ✅ Button disabled while pending
// ✅ Text changes to show loading
// ✅ Must be used in child component
app/contact/page.tsx
import { SubmitButton } from '@/app/components/SubmitButton';
import { submitContact } from '@/app/actions/contact';

export default function ContactPage() {
  return (
    <form action={submitContact} className="space-y-6">
      <input
        name="name"
        placeholder="Your name"
        required
        className="w-full px-4 py-2 border rounded"
      />

      <textarea
        name="message"
        placeholder="Your message"
        required
        className="w-full px-4 py-2 border rounded"
      />

      {/* SubmitButton uses useFormStatus */}
      <SubmitButton />
    </form>
  );
}

// ✅ SubmitButton is child of form
// ✅ Automatically gets pending state
// ✅ No manual state management needed

Submit Button Variations

app/components/SubmitButton.tsx
'use client';

import { useFormStatus } from 'react-dom';

// Basic button with spinner
export function SubmitButton({ children }: { children: React.ReactNode }) {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className="flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg disabled:bg-gray-400"
    >
      {pending && (
        <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
          <circle
            className="opacity-25"
            cx="12"
            cy="12"
            r="10"
            stroke="currentColor"
            strokeWidth="4"
            fill="none"
          />
          <path
            className="opacity-75"
            fill="currentColor"
            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
          />
        </svg>
      )}
      {pending ? 'Submitting...' : children}
    </button>
  );
}

// Button with custom pending text
export function SaveButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className="px-6 py-3 bg-green-600 text-white rounded-lg disabled:opacity-50"
    >
      {pending ? (
        <>
          <span className="inline-block animate-pulse">⏳</span> Saving...
        </>
      ) : (
        <>💾 Save</>
      )}
    </button>
  );
}

// Delete button with confirmation
export function DeleteButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className="px-4 py-2 bg-red-600 text-white rounded disabled:bg-red-300"
    >
      {pending ? 'Deleting...' : 'Delete'}
    </button>
  );
}

// ✅ Spinner during loading
// ✅ Custom icons and text
// ✅ Different button styles
// ✅ Reusable components

Using All useFormStatus Properties

app/components/FormDebug.tsx
'use client';

import { useFormStatus } from 'react-dom';

export function FormDebug() {
  const { pending, data, method, action } = useFormStatus();

  return (
    <div className="p-4 bg-gray-100 rounded text-sm font-mono">
      <p><strong>Pending:</strong> {pending ? 'Yes' : 'No'}</p>
      <p><strong>Method:</strong> {method || 'None'}</p>
      <p><strong>Action:</strong> {action ? 'Set' : 'None'}</p>
      
      {data && (
        <div className="mt-2">
          <strong>Form Data:</strong>
          <ul className="ml-4">
            {Array.from(data.entries()).map(([key, value]) => (
              <li key={key}>
                {key}: {value.toString()}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

// Usage:
<form action={submitForm}>
  <input name="username" />
  <FormDebug /> {/* Shows all form status */}
  <SubmitButton />
</form>

// ✅ Access all status properties
// ✅ Useful for debugging
// ✅ See what's being submitted

useFormState Hook

What is useFormState?

useFormState manages form-level state that updates based on Server Action results. Perfect for showing errors, success messages, or any state that changes with submissions.

useFormState Pattern:

TYPESCRIPT
const [state, formAction] = useFormState(serverAction, initialState);

// state: Current state (updates with Server Action return)
// formAction: Wrapped action to use in form
// serverAction: Your Server Action
// initialState: Starting state value

Server Action Signature with useFormState

app/actions/posts.ts
'use server';

// Server Action for useFormState receives prevState as first parameter
export async function createPost(
  prevState: { message: string } | null,
  formData: FormData
) {
  const title = formData.get('title') as string;

  // Validate
  if (!title || title.length < 3) {
    return {
      message: 'Title must be at least 3 characters',
    };
  }

  try {
    // Create post
    await db.posts.create({
      data: { title },
    });

    return {
      message: 'Post created successfully!',
    };
  } catch (error) {
    return {
      message: 'Failed to create post',
    };
  }
}

// ✅ First parameter: prevState
// ✅ Second parameter: formData
// ✅ Return new state
// ✅ State accumulates across submissions

Basic useFormState Example

app/components/CreatePostForm.tsx
'use client';

import { useFormState } from 'react-dom';
import { createPost } from '@/app/actions/posts';
import { SubmitButton } from './SubmitButton';

const initialState = {
  message: '',
};

export function CreatePostForm() {
  const [state, formAction] = useFormState(createPost, initialState);

  return (
    <form action={formAction} className="space-y-4">
      <div>
        <label htmlFor="title" className="block font-semibold mb-2">
          Title
        </label>
        <input
          type="text"
          id="title"
          name="title"
          required
          className="w-full px-4 py-2 border rounded"
        />
      </div>

      <SubmitButton />

      {/* Display message from state */}
      {state?.message && (
        <p
          className={`p-4 rounded ${
            state.message.includes('success')
              ? 'bg-green-100 text-green-800'
              : 'bg-red-100 text-red-800'
          }`}
        >
          {state.message}
        </p>
      )}
    </form>
  );
}

// ✅ useFormState manages message state
// ✅ formAction used instead of direct action
// ✅ state updates with Server Action return
// ✅ Display success/error messages

Form with Field Errors

app/actions/posts.ts
'use server';

import { z } from 'zod';

const postSchema = z.object({
  title: z.string().min(3, 'Title must be at least 3 characters'),
  content: z.string().min(10, 'Content must be at least 10 characters'),
  category: z.enum(['tech', 'design', 'business'], {
    errorMap: () => ({ message: 'Invalid category' }),
  }),
});

type FormState = {
  message?: string;
  errors?: {
    title?: string[];
    content?: string[];
    category?: string[];
  };
};

export async function createPost(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  // Validate
  const result = postSchema.safeParse({
    title: formData.get('title'),
    content: formData.get('content'),
    category: formData.get('category'),
  });

  if (!result.success) {
    return {
      errors: result.error.flatten().fieldErrors,
    };
  }

  try {
    await db.posts.create({
      data: result.data,
    });

    return {
      message: 'Post created successfully!',
    };
  } catch (error) {
    return {
      message: 'Failed to create post',
    };
  }
}

// ✅ Type-safe state
// ✅ Field-specific errors
// ✅ Success message
// ✅ Zod validation
app/components/CreatePostForm.tsx
'use client';

import { useFormState } from 'react-dom';
import { createPost } from '@/app/actions/posts';
import { SubmitButton } from './SubmitButton';

export function CreatePostForm() {
  const [state, formAction] = useFormState(createPost, {});

  return (
    <form action={formAction} className="space-y-6">
      {/* Title field */}
      <div>
        <label htmlFor="title" className="block font-semibold mb-2">
          Title
        </label>
        <input
          type="text"
          id="title"
          name="title"
          className={`w-full px-4 py-2 border rounded ${
            state?.errors?.title ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {state?.errors?.title && (
          <p className="text-red-600 text-sm mt-1">
            {state.errors.title[0]}
          </p>
        )}
      </div>

      {/* Content field */}
      <div>
        <label htmlFor="content" className="block font-semibold mb-2">
          Content
        </label>
        <textarea
          id="content"
          name="content"
          rows={10}
          className={`w-full px-4 py-2 border rounded ${
            state?.errors?.content ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {state?.errors?.content && (
          <p className="text-red-600 text-sm mt-1">
            {state.errors.content[0]}
          </p>
        )}
      </div>

      {/* Category field */}
      <div>
        <label htmlFor="category" className="block font-semibold mb-2">
          Category
        </label>
        <select
          id="category"
          name="category"
          className={`w-full px-4 py-2 border rounded ${
            state?.errors?.category ? 'border-red-500' : 'border-gray-300'
          }`}
        >
          <option value="">Select category</option>
          <option value="tech">Technology</option>
          <option value="design">Design</option>
          <option value="business">Business</option>
        </select>
        {state?.errors?.category && (
          <p className="text-red-600 text-sm mt-1">
            {state.errors.category[0]}
          </p>
        )}
      </div>

      <SubmitButton />

      {/* Success message */}
      {state?.message && (
        <p className="p-4 bg-green-100 text-green-800 rounded">
          {state.message}
        </p>
      )}
    </form>
  );
}

// ✅ Field-specific error display
// ✅ Visual error states (red border)
// ✅ Success message
// ✅ Type-safe state access

Combining Both Hooks

Complete Form with Loading and State

app/components/TodoForm.tsx
'use client';

import { useFormState } from 'react-dom';
import { useFormStatus } from 'react-dom';
import { addTodo } from '@/app/actions/todos';

// Submit button with useFormStatus
function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className="px-6 py-3 bg-blue-600 text-white rounded-lg disabled:bg-gray-400 flex items-center gap-2"
    >
      {pending && (
        <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
          <circle
            className="opacity-25"
            cx="12"
            cy="12"
            r="10"
            stroke="currentColor"
            strokeWidth="4"
            fill="none"
          />
        </svg>
      )}
      {pending ? 'Adding...' : 'Add Todo'}
    </button>
  );
}

// Form with useFormState
export function TodoForm() {
  const [state, formAction] = useFormState(addTodo, {});

  return (
    <form action={formAction} className="space-y-4">
      <div>
        <input
          type="text"
          name="text"
          placeholder="What needs to be done?"
          required
          className={`w-full px-4 py-2 border rounded ${
            state?.error ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {state?.error && (
          <p className="text-red-600 text-sm mt-1">{state.error}</p>
        )}
      </div>

      <SubmitButton />

      {state?.success && (
        <p className="text-green-600">Todo added successfully!</p>
      )}
    </form>
  );
}

// ✅ useFormStatus in SubmitButton
// ✅ useFormState in form
// ✅ Loading state from useFormStatus
// ✅ Error/success from useFormState
// ✅ Complete user feedback

Multi-Step Form with State

app/actions/registration.ts
'use server';

type RegistrationState = {
  step: number;
  data: {
    name?: string;
    email?: string;
    password?: string;
  };
  errors?: Record<string, string>;
};

export async function handleRegistrationStep(
  prevState: RegistrationState,
  formData: FormData
): Promise<RegistrationState> {
  const currentStep = prevState.step;

  if (currentStep === 1) {
    // Step 1: Name and email
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;

    if (!name || name.length < 2) {
      return {
        ...prevState,
        errors: { name: 'Name must be at least 2 characters' },
      };
    }

    if (!email || !email.includes('@')) {
      return {
        ...prevState,
        errors: { email: 'Invalid email' },
      };
    }

    return {
      step: 2,
      data: { ...prevState.data, name, email },
    };
  }

  if (currentStep === 2) {
    // Step 2: Password
    const password = formData.get('password') as string;

    if (!password || password.length < 8) {
      return {
        ...prevState,
        errors: { password: 'Password must be at least 8 characters' },
      };
    }

    // Complete registration
    await db.users.create({
      data: {
        name: prevState.data.name!,
        email: prevState.data.email!,
        password,
      },
    });

    return {
      step: 3,
      data: { ...prevState.data, password },
    };
  }

  return prevState;
}

// ✅ Multi-step state management
// ✅ Accumulate data across steps
// ✅ Step-specific validation
// ✅ Progressive disclosure
app/components/MultiStepForm.tsx
'use client';

import { useFormState } from 'react-dom';
import { handleRegistrationStep } from '@/app/actions/registration';
import { SubmitButton } from './SubmitButton';

const initialState = {
  step: 1,
  data: {},
};

export function MultiStepForm() {
  const [state, formAction] = useFormState(
    handleRegistrationStep,
    initialState
  );

  if (state.step === 3) {
    return (
      <div className="text-center p-8 bg-green-50 rounded-lg">
        <h2 className="text-2xl font-bold text-green-800 mb-2">
          Registration Complete!
        </h2>
        <p className="text-gray-600">Welcome, {state.data.name}!</p>
      </div>
    );
  }

  return (
    <div className="max-w-md mx-auto">
      {/* Progress indicator */}
      <div className="mb-8">
        <div className="flex justify-between mb-2">
          <span className={state.step >= 1 ? 'text-blue-600 font-semibold' : 'text-gray-400'}>
            Step 1
          </span>
          <span className={state.step >= 2 ? 'text-blue-600 font-semibold' : 'text-gray-400'}>
            Step 2
          </span>
          <span className={state.step >= 3 ? 'text-blue-600 font-semibold' : 'text-gray-400'}>
            Complete
          </span>
        </div>
        <div className="w-full bg-gray-200 rounded-full h-2">
          <div
            className="bg-blue-600 h-2 rounded-full transition-all"
            style={{ width: `${(state.step / 3) * 100}%` }}
          />
        </div>
      </div>

      <form action={formAction} className="space-y-6">
        {state.step === 1 && (
          <>
            <h2 className="text-2xl font-bold mb-4">Personal Information</h2>
            
            <div>
              <label className="block font-semibold mb-2">Name</label>
              <input
                type="text"
                name="name"
                defaultValue={state.data.name}
                className="w-full px-4 py-2 border rounded"
              />
              {state.errors?.name && (
                <p className="text-red-600 text-sm mt-1">{state.errors.name}</p>
              )}
            </div>

            <div>
              <label className="block font-semibold mb-2">Email</label>
              <input
                type="email"
                name="email"
                defaultValue={state.data.email}
                className="w-full px-4 py-2 border rounded"
              />
              {state.errors?.email && (
                <p className="text-red-600 text-sm mt-1">{state.errors.email}</p>
              )}
            </div>

            <SubmitButton>Next Step</SubmitButton>
          </>
        )}

        {state.step === 2 && (
          <>
            <h2 className="text-2xl font-bold mb-4">Create Password</h2>
            
            <div>
              <label className="block font-semibold mb-2">Password</label>
              <input
                type="password"
                name="password"
                className="w-full px-4 py-2 border rounded"
              />
              {state.errors?.password && (
                <p className="text-red-600 text-sm mt-1">
                  {state.errors.password}
                </p>
              )}
            </div>

            <SubmitButton>Complete Registration</SubmitButton>
          </>
        )}
      </form>
    </div>
  );
}

// ✅ Multi-step form with state
// ✅ Progress indicator
// ✅ Data persists between steps
// ✅ Step-specific validation
// ✅ Success screen

Form Hooks Project Structure

Organization of forms with useFormStatus and useFormState

appImportant

Select a file or folder to see details

Form Hooks Best Practices

1. useFormStatus in Separate Component

TYPESCRIPT
// ✅ GOOD: useFormStatus in child component
function SubmitButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>Submit</button>;
}

function MyForm() {
  return (
    <form action={submitAction}>
      <input name="field" />
      <SubmitButton />
    </form>
  );
}

// ❌ BAD: useFormStatus in same component as form
function MyForm() {
  const { pending } = useFormStatus(); // Won't work!
  return (
    <form action={submitAction}>
      <input name="field" />
      <button disabled={pending}>Submit</button>
    </form>
  );
}

2. Type-Safe State with useFormState

TYPESCRIPT
// ✅ GOOD: Type-safe state
type FormState = {
  message?: string;
  errors?: Record<string, string[]>;
};

export async function submitForm(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  // Implementation
}

const [state, formAction] = useFormState<FormState>(submitForm, {});

// Type safety throughout
if (state.errors?.email) {
  // TypeScript knows this is string[]
}

3. Clear State on Success

TYPESCRIPT
// ✅ GOOD: Clear form after success
'use client';

import { useFormState } from 'react-dom';
import { useEffect, useRef } from 'react';

export function MyForm() {
  const [state, formAction] = useFormState(submitAction, {});
  const formRef = useRef<HTMLFormElement>(null);

  useEffect(() => {
    if (state.success) {
      formRef.current?.reset();
    }
  }, [state.success]);

  return (
    <form ref={formRef} action={formAction}>
      {/* Form fields */}
    </form>
  );
}

// Form resets after successful submission

4. Disable Inputs During Submission

TYPESCRIPT
// ✅ GOOD: Disable all inputs during submission
function FormInputs() {
  const { pending } = useFormStatus();

  return (
    <>
      <input name="email" disabled={pending} />
      <input name="password" disabled={pending} />
      <select name="role" disabled={pending}>
        <option>Admin</option>
        <option>User</option>
      </select>
    </>
  );
}

// Prevents editing during submission

5. Show Loading Indicators

TYPESCRIPT
// ✅ GOOD: Multiple loading indicators
function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <div className="space-y-2">
      <button
        type="submit"
        disabled={pending}
        className="w-full px-6 py-3 bg-blue-600 text-white rounded disabled:bg-gray-400"
      >
        {pending ? (
          <span className="flex items-center justify-center gap-2">
            <Spinner />
            Submitting...
          </span>
        ) : (
          'Submit'
        )}
      </button>
      
      {pending && (
        <p className="text-sm text-gray-600 text-center">
          Please wait, this may take a few seconds...
        </p>
      )}
    </div>
  );
}

// Clear visual feedback

Key Takeaways

  • useFormStatus - access form submission status
  • pending property - true during submission
  • Child component required - useFormStatus must be in form child
  • useFormState - manage form-level state
  • prevState parameter - Server Actions receive previous state
  • formAction - use wrapped action in form
  • State accumulation - perfect for multi-step forms
  • Type safety - TypeScript support throughout

What's Next?

You've mastered useFormStatus and useFormState! Next, we'll explore Revalidating Data After Mutations—updating cached data after Server Actions using revalidatePath and revalidateTag, managing cache invalidation, and ensuring users always see fresh data. You'll build apps with instant updates!

We'll cover revalidation strategies, cache tags, on-demand revalidation, and building optimistic UI updates that feel instant.

⚡ Progressive Enhancement

Both hooks work with progressive enhancement! Forms work without JavaScript, and when JS loads, they enhance with loading states and instant feedback. This gives the best of both worlds—reliability and great UX.

Test Your Understanding

Question 1 of 4

What does useFormStatus provide?

Master React form hooks in Next.js! Learn useFormStatus and useFormState for better form UX and state management.

Previous
Form Validation and Error Handling
Next
Revalidating Data After Mutations

Master Next.js Forms

Join 2,000+ developers building responsive Next.js forms. Get the next lesson on revalidating data after mutations - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo