I’m not a fangirl of any single framework. For me, frameworks are tools. Just like I prefer a good, sturdy hammer over a rusty one with holes... I also prefer tools that help me build software that’s maintainable and pleasant to work with. That’s why I like to stay up-to-date, understand how things work under the hood, and not just “use the thing everyone uses”.
I work with Angular on a daily basis — and yes, Angular forces you into a certain architecture (although believe me, you can still build absolute spaghetti in it… ask me how I know 😬).
But I genuinely love React. Whenever I do a side project, I very often reach for it. I adore JSX and how close React feels to plain JavaScript, especially compared to Angular. But… you know what they say: with great power comes great responsibility 😎 And in the wrong hands… that freedom can burn a project.
I’ve seen more than a few projects that weren’t just messy.
They looked like someone wrote them in jQuery, deleted $(document).ready, sprinkled some JSX over it… and genuinely tried to make it work 🤡
Was it a blind migration from an old codebase?
Was it devs who, deep in their hearts, never really left jQuery behind? 😉
Hard to say.
Either way - here are some classic signs your React code isn’t really React… it’s just jQuery wearing JSX. And yes, it shows. Loudly.
1️⃣ One giant component that “does everything”
React file. Looks like .tsx. Technically it has sub-components. Maybe even a utility for CSS class names. But emotionally?
Emotionally, it feels like an old-school index.html with a giant script tag living inside 😂
Big blob. Fetching data. Updating UI. Managing events. Managing layout. Oh, and probably a modal, three dropdowns, a table and a sidebar too, because why not.
🧨 Example
function App() {
const [data, setData] = useState([]);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [filter, setFilter] = useState("");
useEffect(() => {
fetch("/api/items")
.then(res => res.json())
.then(setData);
document.getElementById("loader").style.display = "none";
document.getElementById("list").style.display = "block";
const el = document.getElementById("filter");
el.addEventListener("input", e => setFilter(e.target.value));
}, []);
return (
<>
<input id="filter" />
<div id="loader">Loading...</div>
<ul id="list">
{data.filter(d => d.includes(filter)).map(d => <li>{d}</li>)}
</ul>
<button onClick={() => setSidebarOpen(!sidebarOpen)}>Toggle</button>
<div className="sidebar">{sidebarOpen && "Hello"}</div>
</>
);
}
🤔 Why does this happen?
- Someone migrated from jQuery and just shoved everything into one file.
- “It works, don’t touch it”.
- No architecture decisions early on.
- “We’ll refactor later” (we all know how that story ends).
✅ How it should be
- split components by responsibility (Single Responsibility Principle)
-
separate:
- data / logic components
- presentation components
move logic to custom hooks when appropriate
avoid direct DOM manipulation — let React own the UI
remember: lots of small components > one god-component
2️⃣ One giant useEffect that does… everything
useEffect(() => { /* 🤹♂️ everything happens here */ }, [])
Fetching data, adding event listeners, toggling classes, updating the DOM, talking to 7 different services, scrolling, analytics, toast notifications… all in one glorious effect.
Basically:
Take
$(document).ready, replace it withuseEffect, ship to prod 😎
🧨 Example
useEffect(() => {
fetch("/api/stats")
.then(res => res.json())
.then(data => {
setStats(data);
document.title = "Dashboard";
document.getElementById("counter").textContent = data.users;
});
const resize = () => document.body.classList.toggle("mobile", window.innerWidth < 700);
window.addEventListener("resize", resize);
window.scrollTo(0, 0);
return () => window.removeEventListener("resize", resize);
}, []);
🤔 Why does this happen?
- Lack of mental model: “effect = place where magic happens”.
- Copy-paste from older code.
- “Hey, it runs once, perfect place for everything!”
- No understanding that effects should be focused and scoped.
✅ How it should be
- every effect should have one clear responsibility
- split giant
useEffectinto multiple focused effects - don’t put logic in effects if it can live in render
- remember:
useEffect !== lifecycle method
- avoid stuffing all app behavior into a single “runs once” effect
3️⃣ useEffect for things that should simply be in JSX
This one hurts a little 😅
Doing DOM manipulation in an effect just to update text, class, visibility, or something that JSX can declare naturally.
Instead of:
if condition → change DOM
React wants:
if condition → render something else
🧨 Example
useEffect(() => {
const el = document.getElementById("message");
if (error) {
el.classList.add("visible");
el.textContent = error;
} else {
el.classList.remove("visible");
}
}, [error]);
Meanwhile React:
{error && <p className="message">{error}</p>}
🤔 Why does this happen?
- Still thinking imperatively: “UI is something I change, not something I describe.”
- Old patterns: “Everything dynamic? ➝ must go in useEffect!”
- Old habits die hard.
✅ How it should be
- UI changes should be expressed declaratively in JSX
- show/hide things using conditional rendering
- derive classes from state, don’t toggle them manually
-
think:
- “state changes → React rerenders”
- not “state changes → I patch the DOM”
4️⃣ A giant “switch” on CSS classes instead of real logic
UI state stored… not in state
…not in props
…but… 🥁 in CSS class names.
The application logic becomes:
- “if it has this class then it means it’s open”
- “if it doesn’t then it’s closed”
- “if it has this plus that then it’s in some magic state nobody fully understands anymore”
Congrats, you built a state machine… in your stylesheet 🙃
🧨 Example
useEffect(() => {
const steps = document.querySelectorAll(".step");
steps.forEach(step => {
step.addEventListener("click", () => {
steps.forEach(s => s.classList.remove("active"));
step.classList.add("active");
});
});
}, []);
🤔 Why does this happen?
- Legacy thinking.
- “This used to work in jQuery, why change it?”
- CSS was used as a state holder for years — the habit sticks.
✅ How it should be
- keep UI state in React state or a store
- make UI derive from state instead of encoding logic in CSS
-
for complex flows consider:
useReducer- a proper state machine
-
rule of thumb:
- CSS = styling
- React state = logic
5️⃣ Animations as “just toggle the class in JS”
Need animation?
Add/remove class in JS. Done.
React? Oh yes, still technically there 👀 Just quietly watching.
No state. No declarative transitions. No structure.
Just:
click → add class → remove class later → hope nothing breaks
🧨 Example
function Notification() {
useEffect(() => {
const btn = document.getElementById("show");
const box = document.getElementById("note");
btn.onclick = () => {
box.classList.add("visible");
setTimeout(() => box.classList.remove("visible"), 2000);
};
}, []);
return (
<>
<button id="show">Show</button>
<div id="note" className="notification">Hello!</div>
</>
);
}
🤔 Why does this happen?
- Familiar old pattern.
- Quick hack delivered to production.
- Zero time allocated for frontend architecture.
✅ How it should be
- base animation triggers on React state, not manual DOM
-
simplest option:
- state → class toggle in JSX
-
better options:
- CSS transitions driven by state
- React Transition Group
- Framer Motion
avoid imperatively driving animations when you can declare them
6️⃣ Keeping DOM elements inside state 😱
Rare… but I’ve seen it.
And once you see it, you never really forget it — it leaves a mark 😅
useState<HTMLDivElement | null>(null)
And then logic like:
- “if we have this element, do things directly on it”
- instead of storing intent in state.
That’s not React. That’s absolutely raw DOM puppet-mastering.
🧨 Example
const [el, setEl] = useState(null);
useEffect(() => {
setEl(document.getElementById("target"));
}, []);
function highlight() {
el.style.background = "yellow";
}
🤔 Why does this happen?
- Confusing refs, state, and DOM.
- Treating React as just a helper to render markup.
- Not really understanding React’s role at all.
✅ How it should be
- if you need to access DOM → use
ref - store intent in state, not raw DOM nodes
- DOM is implementation detail
- state describes what you want to happen, not how to mutate the element
💬 Why this matters
I’m not saying code must always be perfect.
Sometimes quick hacks are fine. Sometimes deadlines win. Sometimes legacy constraints are real.
But when React is used like jQuery with JSX…
- maintenance becomes nightmare
- team onboarding becomes painful
- bugs hide in dark corners nobody understands
- architecture collapses under its own weight
React is powerful not because it manipulates DOM better.
It’s powerful because:
✨ State → UI
not
🔧 UI → patch → patch → patch
🤍 A little reflection
React gives freedom. And that freedom is beautiful.
But freedom requires responsibility. Without it, React becomes:
a jQuery with better branding
And that’s a bit sad, because React can give us structure, clarity, predictability — if we let it.
If your project smells like the things above…
it’s not a shame.
It just means there is history, pressure, context, and human decisions behind it.
But maybe it also means…
it’s time to pause, breathe, and rethink a little 😊
🙋♀️ Your turn
I’m super curious:
👉 Have you seen “React in jQuery cosplay” in the wild?
Share your stories, scars, confessions, or survival tips!
Top comments (15)
We could talk about "React in spaghetti cosplay", the same as "jQuery in spaghetti cosplay".
Both are UI libraries, not architectural frameworks, which are separate, orthogonal concerns.
We shouldn't even expect React or jQuery to have a say in an application architecture.
What we should have instead is a formal architecture declared in a project, like in a sort of
architecture.ymlfile that declares architectural decisions, constraints, structure, etc. Some sort of "architectural linters" could assume the role of checking whether code, components, services, adapters etc exist and are created and connected according to the rules... that would bring order to chaos and we would stop blaming the wrong libraries for the wrong problems.Angular does behave more like a framework: it does define structure, architectural elements and their relationships, but those are only defined "anecdotally", rather through a formal system. This is the reason why we can still create spaghetti with it...
Architecture linters (or any kind of formal, enforceable architectural rules) would honestly be an amazing solution and probably the best possible outcome long-term.
At the same time, I don’t think the fact that React is “just a UI library” automatically frees us from thinking about structure, responsibility, and patterns. Treating React purely as a rendering layer for imperative DOM logic misses the point of why many of us choose it in the first place. Even without hard enforcement, there is a “React way” of thinking about UI, state, and effects - and it’s worth trying to follow it.
In practice, that often comes down to things like better code reviews, shared conventions, and calling out patterns that clearly fight the React mental model. These may not be perfect substitutes for formal architectural tooling, but they still help reduce entropy over time.
And of course, reality often intervenes: many projects are inherited, shaped by past decisions, deadlines, and constraints, and the code is simply there already. In those cases it’s less about blame and more about awareness and gradual improvement.
Thanks a lot for the thoughtful comment - I really appreciate it and the broader architectural perspective you’re bringing to the discussion.
Hi Sylwia,
Thanks for this post.
To be frank, DOM manipulation inside useEffects are like a haunting ghost, visiting from old times. I am not very experienced in non-framework web development but mixing two approaches (DOM manipulation with vanilla JS inside a react comp) seems really something to be avoided.
But regardless of oldschool JS coding, the examples showcase how the code can turn to spaghetti or over-complex. I wonder how it would affect the performance, especially in a big codebase.
The "How it should be" parts are of great value.
Best regards
Thanks a lot for the comment - I’m glad you found the post useful.
And yes, performance can definitely suffer, especially in larger codebases. Mixing imperative DOM manipulation with React often leads to unnecessary work and effects that are hard to reason about or optimize. Unfortunately, this pattern isn’t that rare in practice, and it shows up quite often in React projects - likely because of its flexibility and the scale at which it’s commonly used.
A framework is architected in ways that makes using it a guarantee of consistency across developers, teams and companies.
React is none of these, and from seeing their trend in about ten years, then do not care to become. They seem pretty happy with this multitude of malpractices.
You’re touching on a very important - and quite sensitive - point here.
Very often, when you tell React fanboys that React is “just a library”, the immediate reaction is outrage: “That’s not true, it’s a huge ecosystem!” And yes, the ecosystem is massive. But in practice, the lack of strong guarantees around consistency, integration, and architectural direction is very real.
I remember jumping into a React project after several years of working with Angular and being genuinely surprised that one of the most popular UI libraries in the React world was not fully compatible with another extremely popular form library. You had to write custom helpers just to make validation behave correctly. In Angular, a situation like that would be almost unthinkable, because the framework provides stronger, shared abstractions and contracts.
Things have definitely improved over the years - but your point still stands. React seems comfortable embracing flexibility over consistency, and that choice has consequences. It’s powerful, but it also means that a lot of discipline has to come from teams themselves, not from the tool.
This hit a little too close 😅
“jQuery with JSX” is exactly the smell — especially the giant useEffect doing document-ready cosplay.
Loved how you framed it as habits + history, not “bad devs”. React really shines when state → UI is respected.
Great reminder to pause and refactor before the god-component becomes folklore 👏
And yes, exactly that was my intention: it’s rarely about “bad devs”, much more about habits, history, and pressure piling up over time. React really does shine once the state → UI mental model is respected.
Glad it resonated - and hopefully helped stop at least one god-component before it turned into project folklore 👏
One big reason for me to write code in a certain way is maintainability. I know that I have to go the mess I may have created multiple times in the future, and so do my colleagues.
Efforts we spend in creating beautiful code should have the goal in mind to create something an average, hung-over developer would find possible to reason about in an emergency.
Linters, static code analysis and, if possible, code reviews are proven tools to raise the quality bar of a code base.
"I don't want to touch this because it may break" is a huge alarm sign you can get from a developer that the code is crap.
I completely agree with this. For me, maintainability is one of the main reasons to care about how code is written in the first place.
I’m also a strong believer in keeping things as simple as possible and avoiding unnecessary layers of abstraction. If a new developer joins the project, they should be able to understand the code without fear and extend it with confidence - not treat it like a fragile artifact no one dares to touch.
nice!
Thanks Ben 😊
No problem :)
or everything is a custom hook!
Haha, yes! I completely forgot about that architectural gem 😄