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 (24)
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.
I also prefer React.
Good choice š
Crazy thing is Angular compiled code is now FASTER and SMALLER than React. Who would have thought :)
The power of Signals is real
Right?! š For years people kept predicting Angularās imminent death, and now it really feels like itās having a comeback moment. The impact of signals is hard to ignore.
Itās a great reminder that the frontend ecosystem is cyclical - tools evolve, assumptions change, and yesterdayās ādead frameworkā can suddenly become very relevant again.
We loved your post so we shared it on social.
Keep up the great work!
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 should not be surprising. React does have similar fundamental issues to jQuery. More specifically it strongly couples state and rendering, itās very slow and uses excessive memory, lacks a proper routing engine, it treats styling as a third party citizen, it scales extremely poorly both in terms of performance and maintainability. Actually in some of those categories modern jQuery has improved while React keep digressing⦠Canāt wait for the day we talk about it as an unfortunate legacy.
I would suggest looking into Vue, Svelte, Solid or Preact, if you want a framework that actually makes sense.
Thatās a strong take, and I think it highlights something important: most of what youāre describing are trade-offs, not accidental flaws.
React definitely made very opinionated choices early on - especially around coupling state and rendering - and those choices scale well in some dimensions and very poorly in others. Performance, memory usage, and the need for a surrounding ecosystem are all real costs that teams need to be aware of, not ignore.
At the same time, I donāt see React as āthe correctā or āinevitableā solution anymore - just one option among many. Vue, Svelte, Solid, Preact all explore different points in the design space, and in many cases they make more sense, especially when performance or simplicity are primary concerns.
For me, the core issue is less āReact is badā and more āReact is often used without acknowledging its constraintsā. When those constraints are ignored, it absolutely starts to feel like legacy - sometimes faster than people expect.
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.
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 š
Nice post
Thanks a million š
nice!
Thanks Ben š
No problem :)