- FrontendJoy
- Posts
- Don’t do this (unless you want sh*t frontend code)
Don’t do this (unless you want sh*t frontend code)
Avoid These 17 Mistakes if You Want Clean Frontend Code
It’s easy to end up with messy frontend code.
In this post, I’ll show you 17 things to avoid if you want to keep your frontend code clean and maintainable.
Let’s dive in 👇
Mistake #1: Using too many global variables
Global variables are accessible from anywhere in your app, which makes them risky.
If you change one in a random place, you can unintentionally break something elsewhere, and that’s a nightmare to debug.
They also make testing harder, since the function's behavior might change depending on the global state.
Use function arguments or scoped variables instead.
❌ Bad:
let userRole = 'guest';
function canEditPost() {
return userRole === 'admin';
}
userRole = 'admin'; // Changes app behavior unexpectedly
✅ Better:
function canEditPost(userRole) {
return userRole === 'admin';
}
const role = 'admin';
canEditPost(role);
Mistake #2: Poor variable and function names
Using vague names like x
, data
, or handleStuff
slows everyone down.
Readers (including future you) will need to guess what each thing does — or worse, scroll around to find out.
A good rule of thumb: the bigger the scope, the more descriptive the name should be.
Short loops? i
is fine.
Longer scope? Use names that tell the whole story.
❌ Bad:
function d(a, b) {
return a - b;
}
const x = d(100, 50);
✅ Better:
function calculateDiscount(originalPrice, discountAmount) {
return originalPrice - discountAmount;
}
const finalPrice = calculateDiscount(100, 50);
Mistake #3: “God” functions
These functions try to do everything — validate input, update UI, fetch data, handle errors — all in one place.
The result? Long, messy functions that are painful to test and debug.
Instead, split logic into smaller, focused functions.
This improves readability, makes it easier to write unit tests, and helps you reuse logic in other places too.
❌ Bad:
function handleFormSubmit(event) {
event.preventDefault();
const name = event.target.name.value;
const email = event.target.email.value;
if (!name || !email.includes("@")) {
alert("Invalid form");
return;
}
setLoading(true);
fetch("/api/submit", {
method: "POST",
body: JSON.stringify({ name, email }),
})
.then(res => res.json())
.then(data => {
setUser(data.user);
navigate("/dashboard");
})
.catch(() => {
alert("Something went wrong");
})
.finally(() => setLoading(false));
}
✅ Better:
function handleFormSubmit(event) {
event.preventDefault();
const formData = getFormData(event);
if (!isValidForm(formData)) {
alert("Invalid form");
return;
}
submitForm(formData);
}
function getFormData(event) {
return {
name: event.target.name.value,
email: event.target.email.value,
};
}
function isValidForm({ name, email }) {
return name && email.includes("@");
}
async function submitForm(data) {
try {
setLoading(true);
const response = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify(data),
});
const result = await response.json();
setUser(result.user);
navigate("/dashboard");
} catch (e) {
alert("Something went wrong");
} finally {
setLoading(false);
}
}
Mistake #4: Functions with many same-type params
When a function has several parameters of the same type — like booleans or strings — it’s easy to mix them up.
Instead, pass an object with named keys.
That way, it’s clear what each value is for — and you don’t need to jump to the function definition to understand what’s going on.
❌ Bad:
createUser("Alice", true, false, true);
✅ Better:
createUser({ name: "Alice", isAdmin: true, isVerified: false, isActive: true });
Mistake #5: Not cleaning up resources
Whenever you add an event listener, create intervals, set timeouts, or use useEffect
in React, clean them up once they’re no longer needed.
If you don’t, you could end up with memory leaks, stacked event listeners, or ghost network calls.
For frontend apps that run for a long time (such as dashboards or admin panels), these bugs can significantly impact performance — or even crash your app entirely.
❌ Bad (React):
useEffect(() => { window.addEventListener('resize', handleResize); }, []);
✅ Better:
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Mistake #6: Unit-less variables
If a value has a unit — like milliseconds, pixels, or megabytes — include that unit in the variable name.
Otherwise, other devs (or future you) will have to guess what the number means.
❌ Bad:
const timeout = 3000;
✅ Better:
const timeoutMs = 3000;
Mistake #7: Nesting conditionals >3 levels deep
When your logic is nested more than 2 or 3 levels, it becomes hard to read and reason about, especially if you have lots of if/else
blocks.
Instead, try to “flatten” your code:
Return early
Invert the conditions
Split logic into smaller functions
That makes each block easier to read and debug.
❌ Bad:
if (user) {
if (user.isAdmin) {
if (user.hasAccess) {
// do stuff
}
}
}
✅ Better:
if (!user || !user.isAdmin || !user.hasAccess) return;
// do stuff
Mistake #8: Hardcoding URLs or config in the code
Hardcoding things like URLs, API keys, or environment-specific config is a trap.
The moment that value changes, you’ll have to hunt it down across the codebase.
Instead, store config values in constants or environment variables, then import them where needed.
You’ll avoid bugs, desyncs, and hours of wasted time.
❌ Bad:
fetch('https://api.example.com/posts');
✅ Better:
const API_BASE_URL = process.env.API_BASE_URL;
fetch(`${API_BASE_URL}/posts`);
Mistake #9: Keeping dead code “just in case”
We’ve all done it — comment out some code and leave it there “just in case.”
But here’s the truth: 99% of the time, no one will ever use it 😅. And no one wants to scroll past it.
❌ Bad:
// old validation logic (maybe needed later)
// function validateInput(input) { ... }
✅ Better: Just delete it.
Mistake #10: Giant “utils” files
The classic utils.js
file always starts innocent, then slowly turns into a kitchen sink of random functions.
Before you know it, it’s hundreds of lines long and impossible to navigate.
Instead, colocate utility functions near the code that uses them.
Or, split them into focused files like arrayUtils.js
, dateUtils.js
, etc.
❌ Bad:
// utils.js
export function doStuff() {}
✅ Better:
// listUtils.js (next to List component)
export function getVisibleItems(items, filter) {
return items.filter(item => item.visible && item.category === filter);
}
Mistake #11: Swallowing errors
If your code might throw an error, don’t just ignore it.
Swallowed errors are a nightmare to debug, especially in production.
Even a simple console.error
can save hours of guessing.
And if users are affected, show an indication in the UI so they know something went wrong (instead of a blank screen).
❌ Bad:
try {
riskyFunction();
} catch (e) {
// nothing
}
✅ Better:
try {
riskyFunction();
} catch (e) {
console.error("Error in riskyFunction:", e);
showToast("TODO: Useful error message + actions");
}
Mistake #12: One giant file
When everything is contained in a single file — components, logic, styling, and state — the file grows rapidly.
You end up with 800+ lines of scroll hell 🔥.
Instead:
Split things up.
Group related code into folders and give each file a clear role.
It lowers the cognitive load and makes onboarding new devs much easier.
❌ Bad:
// App.js — 1000+ lines of code
✅ Better: Split it up into folders:
/components
/context
/pages
Mistake #13: No linter
Linters like ESLint catch issues before they hit production, from simple bugs to missing React dependencies in useEffect
.
They also help standardize code style across a team.
Please ensure you read and act on the warnings: don’t just slap // eslint-disable
everywhere 😉.
❌ Bad:
useEffect(() => {
fetchData();
}, []); // missing fetchData in deps
✅ Better: Use ESLint + React hooks plugin. It will warn you:
React Hook useEffect has a missing dependency: 'fetchData'.
Mistake #14: Messy folder structure
Your project folder shouldn’t feel like a junk drawer.
When files are scattered randomly, it’s challenging to find anything, and this difficulty is compounded when scaling the app.
Instead, use a structure based on features or domains.
Group related files together so new devs can quickly understand what’s what.
❌ Bad:
/src
App.js
helpers.js
styles.css
randomStuff.js
✅ Better:
/src
/components
/services
App.js
Mistake #15: Commenting obvious code
You don’t need to explain what let count = 0
means.
Trust your reader 😉.
If something’s confusing, try simplifying the logic or using clearer names.
Comments should explain why something is done, not what a line of code does.
❌ Bad:
// Set count to 0
let count = 0;
✅ Better:
let itemCount = 0;
Mistake #16: No comments for weird logic
Sometimes you need to add a workaround or support a legacy case.
That’s fine — but explain why.
Without a comment, your teammates (or future you) will waste time trying to understand what magicFix42(data)
means.
Leave a short note. You’ll thank yourself later.
❌ Bad:
const result = magicFix42(data);
✅ Better:
// Applies patch for backend bug #321. Read ticket for more context
const result = magicFix42(data);
Mistake #17: Rewriting what already exists
Don’t reinvent the wheel.
There are tons of small, reliable libraries built by people smarter than us.
If you can solve the problem with a trusted package, use it.
Just keep your dependencies in check so your bundle size doesn’t balloon (check my post ✨ How I Reduced My React Bundle Size by 30% (With Real Examples)).
🟠 Ok:
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
✅ Easier:
import { debounce} from 'lodash-es';
const debouncedFn = debounce(myFn, 300);
Summary
Frontend code can get messy fast.
Avoid these 17 mistakes, and your code will be easier to read, debug, and maintain.
Your future self—and your teammates—will thank you.

Reply