DEV Community

Nando Rama
Nando Rama

Posted on

Building “Squares” with ChatGPT: From Prompts to a Commercial-Ready App (Day 8 - 8.5)

Day 8 → 8.5: Linked Squares shipped (mostly) — and the React #130 detour that proved why “vibe coding” needs a human driver

Project: fan “Squares” app (Next.js / Firebase)
Theme: shipping a new Linked Squares concept, then spending the back half untangling a gnarly React error in production.


The fun part (morning): Linked Squares 🧩

We added a Linked Squares model so managers can spin up a series of identical grids that share configuration and (optionally) a shared pot:

  • Create Square → auto-creates Series S + first Square P1
  • If demand exceeds capacity, auto-create P2, P3… with same grid, numbers, teams
  • Shared pot (series-level) vs per-square payouts (single grid): we picked shared pot for linked series; lottery fallback if a single grid has unclaimed hits
  • Redirect after create to the square page with a ?created=1 banner and a copy-link / invite CTA

All green locally. Then we hit React error #130 in Preview.


The not-so-fun part (afternoon): React #130 and the loop of doom

Symptom:

Minified React error #130 (“Element type is invalid: expected a string (for built-in components) or a class/function…”)

First instinct (my AI pair):
Chase /pools/[poolId] show page. We stubbed the page to raw HTML and still saw #130. We started combing wrappers, layouts, and error boundaries. Lots of motion, not enough progress.

What changed the game:
We traced the actual user flow end-to-end (Manager ➝ Create ➝ Form ➝ Save ➝ Redirect) and stubbed where the app actually was when it crashed: the CreateSquareForm. With a two-line stub we proved the route was fine. Then we restored sections incrementally.

The real root cause:
Our custom Textarea import resolved to undefined in Preview (Linux, case-sensitive FS). The file/export didn’t match the import casing/shape, so React tried to render undefined as a component → #130.

Switching to the native <textarea> made the page render immediately, confirming the hypothesis. We then added a proper components/ui/Textarea.tsx (correct casing, "use client", named export) and the form rendered.


Why this took longer than it should have

  • Error boundary masked the real throw. Our custom error page broke too, which kept us staring at #130 instead of the underlying message.
  • Mac vs Linux gotcha. Locally, case-insensitive FS lets Textarea vs textarea pass; Vercel Preview fails.
  • Vibe coding stall. We were iterating on the wrong surface (/pools page) while the failure happened earlier (create form).

Lesson: AI can brainstorm quickly, but humans must steer when we start looping. A small, targeted hypothesis (“let’s stub the form itself”) beat broad scanning.


The fix, boiled down

  1. Prove it’s the form: stub the component to pure HTML; if the error persists, it’s higher up. It disappeared → it was the form.
  2. Add a one-line import sanity check:
   console.log('[CSF imports]', { Textarea, Card, Input, ... });
   // Anything undefined? That’s your #130.
Enter fullscreen mode Exit fullscreen mode
  1. Swap to native <textarea> to unblock.
  2. Restore custom UI with correct path + export:
  • File: components/ui/Textarea.tsx (PascalCase, "use client")
  • Export: export function Textarea(...) { ... }
  • Import: import { Textarea } from "@/components/ui/Textarea"

Tiny process improvements we adopted

  • Human “stall detector.” If we repeat a hypothesis twice without new signal, change layers (stub, log, or isolate).
  • Incremental re-introduction. Re-add components one at a time until the error returns; that’s the culprit.
  • Case-sensitive audits before shipping: grep for @/components/ui/textarea vs Textarea.
  • Diff markers that don’t collide with code. Instead of + / - (which you might paste into files), use your custom markers:

    • -!*& = remove
    • +&% = add
  • Error boundary toggle. Temporarily rename app/error.tsx / global-error.tsx to reveal the true stack trace.


A simple debugging playbook (copy/paste)

  1. Stub the exact component where the crash shows up.
  2. Log typeof of imported UI components — any undefined means wrong path/export.
  3. Swap to native HTML for suspect components (e.g., <textarea>) to confirm.
  4. Check casing on paths; Vercel is case-sensitive.
  5. Turn off custom error UIs to see the real runtime error.
  6. Add back code in small chunks until the break reappears.

“Vibe coding” requires a human co-pilot

This was a classic example of why AI + human works best when the human is ready to change tactics. An engineer who can read console output, stub components, and reason about app flow can keep the session from going in circles. Without that, vibe coding risks getting stuck in a hallucinated hypothesis loop.


What we still shipped today ✅

  • Linked Squares concept & series model
  • Shared-pot payout approach (with single-grid lottery fallback)
  • Create flow + invites writing
  • UI consistency (Accordion layout, mobile-friendly form)
  • Fixed Textarea component & eliminated the React #130 blocker

Tomorrow: Refine the create square and manager dashboard UI for linked squares. Onward.

Top comments (0)