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.

  • 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 relationship. When the label points to the input’s , 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.

Codex prompting for this practice

A strong prompt here should keep the scope extremely tight. You want Codex focused on building the input primitive correctly, not drifting into unrelated cleanup or backend edits.

A good prompt should restrict the file list to Input.tsx only, specify label and error behavior clearly, explicitly require ref forwarding, and remind Codex not to touch src/app/api/**. That kind of constraint is especially important in frontend work, where a vague prompt can easily lead to “helpful” but unnecessary changes elsewhere.

Example prompt you should write:

Codex, modify ONLY src/components/ui/Input.tsx. Implement a reusable Input that forwards refs, renders an optional <label> (using htmlFor + id), shows inline error text when error is provided, and applies dashboard Tailwind styling (padding, border, focus ring). Do NOT edit anything under src/app/api/**.

Practice 2: TaskForm Shell (React Hook Form + Zod)

This code block creates TaskForm, the reusable form component that will power both create and edit flows later. It defines the form shape the backend expects, validates it with Zod, and uses your shared Input and Button components so the UI stays consistent.

Codex prompting for this practice

A strong prompt for this practice should keep the form requirements explicit. Since the file mixes validation, UI structure, and reusable props, you want Codex to know exactly which libraries and patterns to use.

A good prompt should restrict changes to TaskForm.tsx only, explicitly require React Hook Form, Zod, and zodResolver, specify which fields are required and which are optional, and mention that Input should be used for title and due date while textarea should be used for content. That level of detail helps prevent the model from producing a form that is technically functional but does not match the lesson’s intended structure.

Example prompt you should write:

Codex, modify ONLY src/components/tasks/TaskForm.tsx. Build a reusable TaskForm using react-hook-form + zod + zodResolver. Validate title/content as required (non-empty) and dueDate as optional. Accept initialValues, submitLabel, and onSubmit. Use <Input> for title and dueDate, a <textarea> for content, and show validation errors inline. Disable submit while submitting. Do NOT edit anything under src/app/api/**.

Practice 3: API Client Wrapper (So Fetch Isn’t Everywhere)

This code block creates a small API client that standardizes how the frontend calls backend endpoints. It always parses JSON, extracts json.data from the backend envelope, and provides helper methods for the common HTTP verbs you’ll use in CRUD flows.

  • The client matches the backend response envelope. Since your API returns objects like { data, meta }, request() consistently extracts so pages do not need to remember that detail every time they fetch. This is a small abstraction, but it removes a very common source of repetitive mistakes.

Codex prompting for this practice

A strong prompt here should be very explicit, because this practice spans both a shared library file and a page that consumes it. You want Codex to implement the wrapper and wire it into the new task page without expanding the scope into unrelated files.

A good prompt should restrict changes to apiClient.ts and /tasks/new/page.tsx only, explicitly require extracting json.data, specify predictable error behavior, and require toasts for both success and failure. Those constraints keep the work aligned with the lesson goal: introduce a shared API helper and use it in a real page flow.

Example prompt you should write:

Codex, modify ONLY:

  • src/lib/apiClient.ts

  • src/app/(dashboard)/tasks/new/page.tsx

In apiClient.ts, implement a request wrapper that parses JSON, returns json.data on success, and surfaces errors in a predictable way. Export an api object with get/post/put/patch/del.

In /tasks/new, render TaskForm and in onSubmit call api.post("/api/tasks", values). Show a success toast on success and an error toast on failure. For now do NOT redirect or revalidate caches. Do NOT edit anything under src/app/api/**.

What You’ll Have After This Unit

By the end of this unit, your app has real CRUD foundations. You will have a consistent Input primitive that can be reused anywhere forms exist, a reusable TaskForm that supports task creation now and task editing later, and a central apiClient that keeps API calls clean and predictable.

You will also have the beginning of real task creation wiring through POST requests with toast feedback. That is an important shift: the app is no longer just rendering and navigating well, it is starting to perform real user actions against the backend in a structured way.

Next up in Unit 2, you’ll turn this foundation into fuller product flows by implementing creation and editing with navigation and polish.

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