Forms and API Basics

Introduction: Welcome to Part 2 — Real CRUD Starts Here

Welcome to Vibe Coding the Frontend: Task Manager CRUD & UX Polish (Part 2) 🎉

In Part 1, you built a strong UI foundation. You now have a dashboard shell with navigation, reusable components like TaskRow and Button, URL-driven filtering, polished stub routes, and a lightweight toast system that already makes the app feel more interactive.

In Unit 1: Forms and API Basics, you’ll start building the pieces that make real CRUD flows clean and consistent. Instead of jumping straight into large create and edit pages, you’ll first build the reusable primitives those pages depend on. That includes a reusable Input primitive with label, error handling, and ref forwarding, a reusable TaskForm that defines the shape and validation of task data, and a small API client wrapper so fetch(...) logic is not scattered across the codebase.

These building blocks matter because the next two units will use them repeatedly. Once they exist, you can implement full task creation and editing more cleanly, and then move into delete flow and UX polish without rewriting form and request logic in every page. This is the same philosophy you used in Part 1: build small reusable pieces first, then use them to make the app feel real.

Previously: Where We Left Off (End of Part 1)

In the previous unit, you let the URL drive the UI. Filter buttons updated /tasks/filter?completed=true|false, the filter page validated query params and rendered filtered lists using TaskRow, stub routes were upgraded so they felt intentional instead of broken, and a toast system added friendly feedback across the dashboard.

That matters because Part 2 keeps the same overall philosophy. You are still aiming for small reusable primitives, predictable data flow, and clear boundaries between frontend and backend. The frontend consumes src/app/api/**, but it does not rewrite it. The goal is not just to make CRUD “work,” but to make it work in a way that stays organized as the project grows.

Practice 1: Reusable Input Primitive

This code block implements the Input component used across the course. It gives you a consistent, dashboard-friendly input with an optional label, inline error styling, and ref forwarding so form libraries like React Hook Form can connect to the real DOM input safely.

TypeScript
// src/components/ui/Input.tsx
import { InputHTMLAttributes, forwardRef } from "react";

type Props = InputHTMLAttributes<HTMLInputElement> & {
  label?: string;
  error?: string;
};

export const Input = forwardRef<HTMLInputElement, Props>(function Input(
  { label, id, error, className = "", ...props },
  ref
) {
  const base =
    "w-full rounded-md border bg-white px-3 py-2 text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2";
  const normal = "border-gray-300 focus:border-blue-500 focus:ring-blue-500/30";
  const danger = "border-red-300 focus:border-red-500 focus:ring-red-500/30";

  return (
    <div className="block text-sm">
      {label && (
        <label htmlFor={id} className="mb-1 block text-gray-700">
          {label}
        </label>
      )}
      <input
        ref={ref}
        id={id}
        className={`${base} ${error ? danger : normal} ${className}`}
        {...props}
      />
      {error && <p className="mt-1 text-sm text-red-600">{error}</p>}
    </div>
  );
});
  • Ref forwarding is the library-friendly design choice here. React Hook Form and similar tools often need direct access to the underlying <input> element, and forwarding the ref makes that integration smooth and predictable. Without that, form libraries may still work in simple cases, but you are more likely to run into awkward wiring or limitations later.

  • Error styling is handled in one place, which is exactly what a reusable primitive should do. Instead of every page inventing its own version of “red border plus red message,” the Input component switches between normal and danger styles whenever error is present. That keeps validation feedback visually consistent across the app.

  • Accessibility comes from the label, htmlFor, and id relationship. When the label points to the input’s id, clicking the label focuses the input, and assistive technologies can announce the label correctly. This is a small detail in code, but it has a real effect on usability and quality.

This component may look simple, but it solves a repeated UI problem well. Once it exists, form pages can focus more on form behavior and less on re-creating the same input markup over and over.

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