Learn Build a Real SaaS with Claude Code End-to-End Wiring: The Respond Flow

End-to-End Wiring: The Respond Flow

Intermediate 🕐 26 min Lesson 11 of 25
What you'll learn
  • Wire a client form to a Server Action using useActionState for pending/success/error state
  • Use .bind() to partially apply a Server Action argument that doesn't come from form fields
  • Recognize that Server Actions need the same auth/authorization checks as a Route Handler, since both are reachable via direct POST
  • Use revalidatePath to refresh server-rendered pages after a mutation without client-side refetching
  • Explain why this app intentionally has both a REST endpoint and a Server Action implementing similar logic
  • Trace one feature (respond to a review) across all the pieces built in lessons 8-10

Closing the Loop

Lessons 8-10 built the pieces in isolation: a Route Handler that can save a response, a detail page that displays a review, and the case for keeping state minimal. This lesson wires them into one feature a reader can actually click through -- typing a reply on a review and watching it post without a full page reload.

The Client Form: respond-form.tsx

app/dashboard/reviews/[id]/respond-form.tsx is the one component in this section that genuinely needs "use client", because it needs to show a pending state while the request is in flight and a success state afterward, without navigating away:

"use client";

import { useActionState } from "react";
import { respondToReview, type RespondFormState } from "./actions";

const initialState: RespondFormState = {};

export function RespondForm({ reviewId }: { reviewId: string }) {
  const action = respondToReview.bind(null, reviewId);
  const [state, formAction, pending] = useActionState(action, initialState);

  if (state.success) {
    return 

Response posted.

; } return (