This article is published in English.
When AI Writes Your React App But Skips Clean Code Principles
Learn seven clean code habits—DRY, single responsibility, guard clauses, and more—that AI-generated React code often violates and how to fix them.
A client recently handed off a project that involved building a pizza ordering website.
The scope sounded manageable at first.
A landing page.
A menu section.
Different pizza categories.
Individual product pages.
A shopping cart.
A checkout flow.
Plus an admin panel to handle everything behind the scenes.
The natural assumption was:
"This should be quick."
Then came the decision to lean on an AI coding assistant.
That's where things got interesting.
Letting AI Handle Most of the Coding
The project was built in React.
Instead of typing every line manually, prompts were sent to AI to generate each piece.
A prompt like:
"Build the pizza card component."
produced the component instantly.
Next:
"Build the shopping cart."
Handled.
Then:
"Build the checkout screen."
Also handled.
Then:
"Build an admin dashboard showing stats, orders, and customer info."
Delivered.
The speed was genuinely impressive.
Work that would normally take hours was appearing within minutes.
The result included:
- React components
- Forms
- Data tables
- Dashboard widgets
- API integration
- Loading indicators
- Error handling
- Layouts that adapted to screen size
And the application actually worked.
Everything looked promising.
It seemed like the ideal workflow had been found:
Idea → Prompt → Code → Ship
But one crucial step was missing.
Reviewing what got built.
The Client Never Saw the Code Itself
From the client's point of view, everything looked great.
The site ran smoothly.
The dashboard functioned.
The menu displayed correctly.
Orders showed up as expected.
Visually, it was polished.
So where was the catch?
The problem wasn't visible from the outside.
Under the hood, the codebase was quietly turning into a maintenance headache.
It wasn't obvious right away.
But as more features got layered on, a pattern started to emerge.
A recurring thought kept coming up:
"Wait... hasn't something almost identical to this already been built?"
That's when an important realization surfaced:
AI is genuinely skilled at producing working code.
But clean code isn't something it delivers automatically.
Problem #1: Repeated Logic Everywhere
Nearly identical logic turned out to be scattered across the app.
For instance, several separate functions were calculating totals independently.
One looked like this:
const total = price * quantity;
Elsewhere, a nearly identical version appeared:
const orderTotal = price * quantity;
And in yet another file:
const cartAmount = price * quantity;
All three were essentially performing the same calculation.
Nothing was technically broken.
But the moment the pricing rules needed to change, every single copy would need to be tracked down and updated.
That's the point where the first principle of clean code became clear.
1. DRY — Don’t Repeat Yourself
Whenever the same logic shows up repeatedly, the question worth asking is:
Could this be consolidated into a single source?
For example:
const calculateItemTotal = (price, quantity) => {
return price * quantity;
};
From that point on, every part of the app can call the same shared function.
The goal isn't to force reusability everywhere.
It's simply to eliminate duplication that serves no purpose.
Problem #2: One Component Was Doing Everything
At some point, one of the generated React files was opened for a closer look.
It kept growing.
Line after line.
It just kept expanding.
That single component was responsible for:
- API requests
- Form state management
- Validation logic
- Modal visibility
- Rendering a table
- Filtering results
- Pagination
- Notifications
In short, one file was essentially running half the application on its own.
The obvious conclusion was:
"Maintaining this is going to be painful."
So the component was broken apart into smaller pieces.
Rather than keeping this structure:
Orders.jsx
↓
800+ lines
it was restructured toward something like:
Orders
├── OrderFilters
├── OrderTable
├── OrderRow
├── OrderModal
└── useOrders
That shift led directly into the next principle.
2. Single Responsibility
A component or function should have one clear job.
If you can't describe what a component does in a single sentence, there's a good chance it's carrying too much weight.
For instance:
OrderTablerenders a list of orders.
That's a clean, focused description.
Compare that with:
OrderTablefetches orders, checks user permissions, calculates totals, triggers notifications, and displays everything in a table.
That kind of description is a red flag.
Problem #3: Nested If Statements Took Over the Code
At one point, some logic in the project looked like this:
if (user) {
if (user.isActive) {
if (user.isVerified) {
// create order
}
}
}
It functioned correctly.
But scanning through it was tiring.
So it was rewritten as:
if (!user) return;
if (!user.isActive) return;
if (!user.isVerified) return;
// create order
That version was far easier to follow at a glance.
This is where the next habit comes in:
3. Early Returns / Guard Clauses
Rather than burying the core logic under several layers of nested conditions, it helps to deal with invalid cases upfront and exit early.
Invalid?
↓
Return
Invalid?
↓
ReturnEverything okay?
↓
Do the actual work
The result is that the main logic stays flat, readable, and free of unnecessary indentation.
Problem #4: AI Suggested New Components for Things That Already Existed
This mistake was a little embarrassing.
The prompt given to the AI was:
"Create a confirmation modal for deleting an order."
The AI responded by generating a brand-new component.
It was nearly accepted without question.
Then a quick search through the project turned up something important.
A modal component already existed.
It could simply have been reused.
That moment made something clear:
AI doesn't automatically know what already exists in a given codebase.
And even when it does know, it may still choose to build something new instead of reusing what's there.
A better approach is to prompt with something like:
"Before creating a new component, check the existing codebase for anything that can be reused."
That experience points to another rule:
4. Reuse Before Creating
Before adding:
- a new component
- a new utility function
- a new custom hook
- a new API helper
ask:
"Does this already exist in the project?"
A clean codebase isn't defined by having dozens of reusable pieces sitting around.
It's defined by developers who reuse what's already available instead of quietly duplicating it.
Problem #5: Variable Names Were Sometimes Terrible
AI-generated code arrives fast.
And it's easy to accept variable names like:
const data = ...
const result = ...
const x = ...
const temp = ...
These names compile fine and cause no errors.
But months later, what does data actually refer to?
Is it pizza-related data?
Order information?
Customer records?
Something tied to a dashboard?
So naming should become more intentional as a project grows.
Instead of writing:
const data = await getOrders();
a clearer version is preferable:
const orders = await getOrders();
And instead of:
const x = users.filter(...);
this reads much better:
const activeUsers = users.filter(...);
That produces another straightforward rule:
5. Use Names That Explain the Code
Descriptive naming can often eliminate the need for comments entirely.
Reading something like:
const activeUsers = users.filter(
(user) => user.isActive
);
already tells you exactly what's happening.
There's no need to add:
// Filter users who are currently active
The code speaks for itself.
Problem #6: Optimizing Things That Didn't Need Optimization
At some point in a project like this, it's easy to start thinking:
"This code needs to be more optimized."
That often leads to digging into things like:
useMemo()
useCallback()
and a handful of other performance tricks.
But it's worth pausing here.
Optimization is not automatically a good thing.
Take a trivial calculation like:
const total = price * quantity;
There's no reason to wrap that in some elaborate optimization pattern just because the tools exist.
Doing so would only make the code harder to follow.
Which points to another lesson:
6. Optimize Only When There Is a Real Problem
Don't reach for optimization because:
"Someone online said this technique is faster."
Instead, locate the actual bottleneck first.
Is a component re-rendering too often?
Is an API call slow?
Is a calculation genuinely expensive?
Is the bundle size too big?
Is a database query inefficient?
Identify the real issue before touching anything.
Only then should optimization happen.
Clean code plus unneeded optimization equals unnecessary complexity.
Problem #7: No Longer Asking AI to "Fix Everything"
This might be the most important lesson from a project like this.
At one point, the temptation is to simply say:
"Just clean up the whole project for me."
But it's worth holding back from that.
What does "clean" even mean in that context?
AI could respond by:
- renaming variables and files
- reorganizing the folder structure
- introducing new abstractions
- merging functions together
- deleting code it deems unnecessary
- restructuring the architecture
Some of those changes could genuinely help.
Others might be pointless.
And some could quietly introduce bugs.
A slower, staged approach works better.
Start by asking:
"Look at this project, but don't change anything yet."
Then:
"Point out any duplicated logic."
Then:
"Tell me which of these duplicates are actually worth fixing."
Only after that should the suggestions get reviewed.
Then one change gets applied.
Then it gets tested.
That becomes rule number seven:
7. Understand Before You Refactor
Never let AI touch code that isn't fully understood first.
Start by analyzing.
Then make sure the logic is understood.
Then decide what to do.
Then make the change.
Then verify it with a test.
What One Pizza Website Illustrates
Interestingly, a client rarely asks:
"Is your code clean?"
The question is usually simpler:
"Does the site work?"
And often it does.
Still, developers carry a responsibility beyond that first working version.
Software rarely stays static once it ships.
Eventually a client comes back with:
"Can we add delivery tracking?"
Then:
"Let's add discount codes."
Then:
"We need support for multiple branches."
Then:
"Add accounts for restaurant staff."
Then:
"We'd like some reporting."
Before long, that small pizza site has grown into a full-fledged system.
That's exactly the point where clean code starts to justify the effort.
AI Wasn't the One at Fault
It's worth being fair here.
AI isn't to blame.
It's genuinely valuable throughout a build like this.
It helps with:
- moving faster
- testing out different approaches
- handling repetitive coding tasks
- tracking down bugs
- scaffolding components
- reasoning through solutions
The real issue is never that AI generates code.
It's that generated code sometimes gets accepted without close enough examination.
Those are two very different problems.
A Revised Workflow for Coding With AI
After a project like this, a shift in approach makes sense.
The process can look roughly like this:
Understand the requirement
↓
Explore existing code
↓
Plan the solution
↓
Ask AI for implementation
↓
Review the generated code
↓
Simplify
↓
Test
↓
Refactor if necessary
AI still handles a large share of the actual typing.
But more of the thinking shifts back to the developer.
That balance seems to be where software development as a whole is headed.
The AI Writes Code, but the Codebase Is Still Yours
That's the core takeaway from this whole experience.
Once AI starts producing your code, it's tempting to assume:
"The AI wrote this, so it must understand what it's doing."
That assumption doesn't hold up.
Ownership of the codebase stays with you.
You're the one who maintains it going forward.
You're the one who has to track down bugs when they surface.
You're the one who needs to explain how it works to someone else.
You're the one who will revisit it months later and need to make sense of it again.
At some point, another developer might open the project and wonder:
"What was the reasoning behind this approach?"
Ideally, the code itself should make that reasoning clear.
Seven Clean Code Habits Worth Holding Onto
Boiling this whole experience down, here's what stuck with me:
1. DRY
Avoid duplicating the same logic in multiple places without good reason.
2. Single Responsibility
Make sure each function or component has one well-defined purpose.
3. Early Returns
Cut down on nesting so the primary logic path stays easy to follow.
4. Reuse Before Creating
Look for existing solutions in the codebase before building something new.
5. Good Naming
Choose names that clearly convey what a variable or function represents.
6. Optimize With Evidence
Hold off on adding complexity unless there's a measurable performance issue to justify it.
7. Understand Before You Refactor
AI can propose modifications, but the call on which ones are worth implementing is yours.
Closing Thoughts
Going into the pizza site project, I assumed the main benefit of AI assistance would be:
raw speed.
Looking back, I've come to believe there's a bigger benefit.
AI frees up mental bandwidth for the aspects of development that genuinely demand judgment.
Instead of burning effort on boilerplate, we get to redirect that energy toward better questions:
Is this actually the right approach?
Could this be made simpler?
Does something like this already exist in the project?
Will this design hold up when the next feature arrives?
Would a different developer be able to follow this?
That's the real difficulty in working with AI-assisted development.
AI is capable of producing hundreds of lines of code almost instantly.
Confirming that those lines actually earn their place in the codebase is still on us.
Perhaps that's the updated definition of clean code now that AI is part of the process:
Getting the code to run isn't the finish line. Being able to maintain it later is.