<p>My last “breakup” was with AI-generated boilerplate—autocomplete that promises to solve your problems but leaves you copy-pasting code you barely understand.
</p><p>I’m miserable, because somewhere along the way we stopped researching. We stopped thinking. We click “generate,” we paste, we ship. My head feels clogged with half-remembered StackOverflow snippets and half-baked TypeScript errors. That ends tonight. Because this time, it’s just me and my half-charged laptop.
</p><p><strong>The Signup Flow: Not Just a Feature, it's a Gatekeeper
</strong></p><p>When you got that invite link to sign up on TwoCents, I know what you saw: a clean form. You dropped in your email, typed a password (maybe a weak one—we’ll talk about that), and hit submit. Magic happened. Boom. You were in.
</p><p>That’s the illusion. Behind the scenes, that form is the first handshake, the velvet rope, the bouncer at the club. You don’t get into the app without impressing him. And I was about to build this exact functionality from scratch—one field, one thought, one bug at a time.
</p><p>But first...
</p><p>We have to crack open the Next.js docs, not because I’m a good student, but because I’m tired of guessing. I need to know how this framework breathes.
</p><p>“By default, layouts and pages are Server Components…”
</p><p>Which means: all that beautiful HTML? Rendered on the server. Efficient. Fast. Great for SEO. But if I want buttons to click, inputs to respond, and browser APIs to fire—guess what?
</p><p>“Use Client Components to layer in interactivity.”
</p><p>Boom. That’s our key. We slap "use client" at the top of signup/page.tsx, and like a magician flipping to the last page of a spellbook, we’re suddenly client-side. Welcome to the browser. Welcome to the chaos.
</p><p>In your page.tsx, slapping a good ol’ rafce in your code editor world spin up a react functional component, this sins up the initial frame work of a functional component— easy, but that’s not even where the madness comes in. You have to think User Interaction, you have to think data.
</p><p>In this party you have to provide your email and password, don’t worry about who you came with or who’s vouching for you, we’ve got that covered on our backend.
</p><p>An action must be made, which is, on this page, the user clicks sign up, like they're consenting to a creed—it's set in stone.</p><p>At the moment, I'm fiddling with TypeScript here—because JavaScript is chaos, a playground for bugs. Types catch mistakes before they explode at runtime, and typing makes your code readable and predictable. I can breathe.</p><p>Backend is more straightforward and logical. Frontend? A complete mess, a maze of folders—that's if you haven't made enough mistakes and gone through a dump of StackOverflow prompts, GitHub forums—amateur stuff.</p><p><br></p><p>First of all, let's define the types of data we will be working with and how we will use them. You control the narrative here; you know what to expect, what every data's final form would be.</p><p>We can declare an interface and tell our page to point to that interface and pass that interface as a property to that page so it'd be able to use it—quite the roundabout method.</p><p><br></p><p>An interface can be declared as an object. Objects are things inside the curly braces—common knowledge. So moving forward, we can create a SignupFormData that will house the data, which is the email and password, that we declare as strings—I'm wasting time.</p><p><br></p><p>Great—my first interface done. But how do I export it to be reusable? I prepend export to my interface. Now any file can import it. Feels good to modularize.</p><p><strong>Now Let's Think Logic</strong></p><p>What are we going to do with the data users provide on this page? We are likely giving somebody this data, and that person registers us as somebody he's seeing for the first time. Yes, there's a person, and he's in the database, using Prisma as the intermediary—the friend that knows another friend. But don't sweat it; we talked about this in the last blog post. Refer to it here: https://www.twocents.space/insight/bits-and-pieces-part-2-rethinking-auth-1480/</p><p>So we need a function to fire when we press sign up on this page. We need to keep track of what the form holds. We need to find a way to connect the backend, make it use our data, and register us.</p><p><br></p><p>Did someone say keep track? I know a guy for that—I swear I'm not grinning.</p><p>Ah yes, the infamous React advice: "Just use useState!" Like it's some sort of holy oil you can rub on every bug and call it a day.</p><p>I set up my state. We're basically telling React by doing this that:</p><p>"Hey, I'm going to keep track of two pieces of information here: the user's email and password. Please store that in a single state object called formData."</p><p>That works fine, but managing each field separately gets messy if your form grows. If you add 3, 5, or 10 more inputs (like username, confirmPassword, phone, etc.), it becomes chaos.</p><p>So instead, we group everything under one object called formData. This allows us to:</p><p>Update all fields through one function: setFormData</p><p>Pass the whole form to an API easily using JSON.stringify(formData)</p><p>Write more scalable and clean code</p><p><br></p><p>Easy. Predictable. Wrong? Not yet.</p><p><strong>How Do We Update This Object When Someone Types?</strong></p><p>We create a function that handles the update or the change. We can call it handleChange and tell it in a curly brace:</p><p>"Take whatever the user typed (value) and update the key that matches the name of the input (like email or password). Keep all the other keys the same."</p><p>We use the spread operator (...prev) to copy the previous values so we don't lose them. Without that, we'd overwrite the whole object. Very important to know.</p><p>When someone types in the email field, handleChange gets called. Let's say they typed "test@example.com":</p><p>e.target.name is "email"</p><p>e.target.value is "test@example.com"</p><p>So setFormData(prev => ({ ...formData, [e.target.name]: e.target.value })) becomes: setFormData(prev => ({ ...prev, [email]: test@example.com }))</p><p>Well, not quite. You get slapped by TypeScript like you're playing chess to check the validity of types: "Name doesn't exist on type 'EventTarget'." Oh.</p><p>45 minutes of Googling: "TypeScript event.target.name," "React input change TypeScript best practice," "why does my code hate me."</p><p>Cool. But now you start wondering—why does every blog post treat this like common sense? Why does nobody tell you that this piece of crap breaks if your name attributes don't perfectly match your keys?</p><p><strong>State Management: You're On Your Own, Kid</strong></p><p>React doesn't care how you manage state. It hands you useState like a spoon and says "good luck with the soup."</p><p>You want formData to update when users type? Then you better babysit every input. You better match keys. You better never typo a name, or your whole flow collapses like a Jenga tower in a hurricane.</p><p>And don't even think about storing this in some magical global state—no Redux, no Zustand, just vibes and local state for now. I want to feel everything.</p><p>The Moment: You Click "Submit"</p><p>The button stares back at you. It doesn't know what it's about to do. I didn't either.</p><p>Simple enough. But let me tell you something: getting that working cost me 3 hours, 17 tabs, and a temporary spiritual crisis.</p><p>Because the first time I sent a POST request? The server 500'd. Why? I didn't stringify the body. My backend was getting [object Object]. Nice.</p><p>Then I forgot to set headers. Then I forgot to await the response. Then TypeScript yelled at me because formData was any. I fixed that by defining an interface. Which felt like progress. Until I forgot to export it and spent another 10 minutes wondering why my types were "missing." Each mistake a lesson in humility.</p><p>Let's talk about how we refined and browsed our way through the handleSubmit function.</p><p>So what now if we press sign up? What happens? Of course, that's after declaring that this is a function. Inside the body, what should execute?</p><p><strong>Googling Like My Job Depends On It</strong></p><p>Every problem spawns a search:</p><p>"Why is fetch body empty?"</p><p>"TypeError: EventTarget has no property 'name'"</p><p>"Await response.json() missing await"</p><p>Each mistake a lesson in humility again.</p><p>I litter my mental notes with snide remarks—"Of course you need it in a try and catch because sometimes they might catch a bug, and you have to be ready." Create a house for the data you'll get from calling your API with the fetch method, and since it's a POST request, you have to specify the method when you're trying to call the API. The response you get is your data, which is usually an email and password that is hashed. Our formData will be updated with the information that is coming from the body of the response that is coming back from initiating a user creation with Prisma. We JSON.stringify, and even send headers to prevent CORS meltdown. I'm not a mind reader!"—but each search is fuel. Each answer is a tiny victory stamp in my brain.</p><p>Exported interface… oh wait, forgot to export it. Back to Googling.</p><p><strong>Meeting Prisma—My Database Wingman</strong></p><p>Once the form data leaves the client, it lands in my API route. I envision:</p><p>Receive JSON</p><p>Validate email and password exist</p><p>Hash password with 12 salt rounds</p><p>prisma.createUser(email, hashedPassword)</p><p>Respond with userId</p><p>No SQL gymnastics, no magic—just a straight line from typed fields to secure database entry. It feels clinical. It feels right.</p><p><br></p><p><strong>Victory Lap</strong></p><p>I lean back. The signup flow hums. No AI slop. No mystery snippets I can't explain. Every piece—from state object to handleSubmit ritual—was thought through, typed by me, debugged by me.</p><p>This wasn't just a signup, this was me mastering my craft.</p>
Bits and Pieces Part 3: Breaking Up with Copy a...
By
Matthew Okadinya
•
7 plays