Introduction: Turning CRUD Into a Real Product Experience

In Unit 1, you built the foundations that make CRUD pleasant instead of painful. You now have a reusable Input primitive, a reusable TaskForm with validation intent, a central API client that understands the backend { data, meta } envelope, and toast feedback so actions feel intentional instead of silent.

Now in this lesson, you’ll take the next step: true interactivity.

By the end of this lesson, your Task Manager will feel much more like a real app. Users will be able to create tasks and land back on the list, edit tasks directly from the detail page, and toggle completion from the list with updates that feel immediate. That shift matters because CRUD is not only about sending requests successfully. It is about giving users a smooth sense that the interface is reacting to what they do.

As always, one important boundary stays in place: don’t touch src/app/api/**. The backend is read-only. Your job in this lesson is to build the frontend product layer on top of it, using the API contract that already exists.

Quick Recall: The Pattern We’re Repeating

Almost everything in this lesson follows the same product-grade loop. First, collect validated values with TaskForm. Then call the backend through api. After that, show toast feedback for success or failure, revalidate the pages that display tasks with mutate(...), and navigate the user to the next sensible screen when appropriate.

That is the difference between “it works” and “it feels like an app.” A page that only submits data may be technically correct, but a page that also refreshes stale views, shows feedback, and moves the user forward feels polished and trustworthy. You will repeat that same loop several times in this lesson, which is why building the reusable primitives first was so important.

Practice 1: Production-Ready Create Flow (/tasks/new)

This code block upgrades /tasks/new into a complete create experience. It uses TaskForm with api.post, shows toast feedback, revalidates the task list, and navigates back to /tasks so the user immediately sees the result of their action.

  • api.post("/api/tasks", values) is the single source of truth for creation. The page does not manually build fetch(...) calls or parse response shapes itself. That is exactly why the API client exists: page files should focus on flow and UX, not on low-level networking details.

  • The toast is the user-facing result of the action. Whether the request succeeds or fails, the user gets immediate feedback instead of being left to guess what happened. Small feedback moments like this are what make the app feel responsive and deliberate.

  • mutate("/api/tasks") keeps the list fresh. When the user returns to /tasks, the cache has already been revalidated, so the newly created item appears without requiring a manual refresh. That makes navigation feel connected to the data layer, rather than like a separate concern.

Codex prompting for this practice

You must keep Codex tightly scoped to one file and describe the success and failure flow explicitly. This is important because create pages often tempt the model to modify related components or backend files “just to help,” which is exactly what you want to avoid.

A good prompt should make it clear that TaskForm and api.post("/api/tasks", values) must be used together inside onSubmit, and that success should trigger toast → mutate → redirect while failure should only show an error toast. That sequence is the real lesson goal, not just “make creation work.”

Example prompt you should write:

Codex, modify ONLY src/app/(dashboard)/tasks/new/page.tsx. Use TaskForm and api.post("/api/tasks", values) inside onSubmit. On success: show a success toast, call mutate("/api/tasks"), and router.push("/tasks"). On failure: show an error toast. Do NOT edit anything under src/app/api/**.

Practice 2: Real Edit Page (/tasks/[id])

This code block turns the detail route into an actual edit page. It fetches the task by id with SWR + api.get, pre-fills TaskForm, submits updates through api.put, and revalidates both the list and the specific task view.

  • SWR makes the edit page data-aware. The page has explicit loading and error states, which means it can respond appropriately before the task data arrives and if something goes wrong. Once the task is available, that data becomes the initialValues for the form, which gives the edit experience its “prefilled” feel.

  • api.put(key, ...) uses the same resource path that the page fetched. That keeps the mental model simple: the task you loaded is the task you are updating. When the fetch key and update target line up cleanly, the flow becomes easier to reason about and easier to maintain.

Codex prompting for this practice

You must scope Codex to the detail page file and describe the flow clearly: fetch, prefill, update, revalidate, and toast. That exact sequence is what makes this a true edit experience rather than just “a page with a form.”

A strong prompt should explicitly require useParams() for the id, SWR for fetching, loading and error UI, TaskForm with initialValues, api.put(...) on submit, and revalidation of both the list and the individual task endpoint. Clear prompting matters here because this page connects several concepts at once.

Example prompt you should write:

Codex, modify ONLY src/app/(dashboard)/tasks/[id]/page.tsx. Use useParams() to get the id, fetch the task via SWR using api.get("/api/tasks/" + id), show loading/error UI, and render TaskForm with initialValues. On submit call api.put("/api/tasks/" + id, values) and show success/error toasts. Revalidate with mutate("/api/tasks") and mutate("/api/tasks/" + id). Do NOT edit anything under src/app/api/**.

Practice 3: Instant Completion Toggle (Optimistic UI in TaskRow)

This code block adds a completion toggle directly in each TaskRow and makes the interaction feel instant. The UI updates optimistically first, then calls the backend with api.patch, and finally revalidates the relevant caches, including filtered task views when those keys are provided.

Recap: What This Lesson Unlocks

After this lesson, your Task Manager has real CRUD product behavior.

The create flow now feels complete: submit the form, show a toast, revalidate the list, and redirect back to /tasks. The edit flow is now real too: fetch by id, prefill the form, save changes, and refresh both local and list data. And the toggle flow adds an extra layer of polish by making completion updates feel instant with optimistic UI, followed by cache refreshes to keep everything correct.

That is the backbone of a modern app. At this point, your UI is no longer just displaying data nicely. It is letting users act on that data in ways that feel responsive, consistent, and intentional.

Next up, you’ll expand this into more complete workflows, including delete behavior and another layer of UX polish so the whole app feels even more seamless.

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