Have you ever worked with jQuery? Do you remember how we used to bind event handlers to elements? Did you know how jQuery event handling worked?
There’s something quietly revolutionary about jQuery’s on()
method.
It doesn’t get paraded around in blog posts anymore, but it should — because if developers had truly understood its full potential, the entire ecosystem of frontend libraries might have evolved differently.
Today, lets recap what we used to do with jQuery. Earlier when jQuery was introduced and became popular this method was widely used. Back in the early days of dynamic interfaces, you’d bind events like this:
$('#container').click(function() {
// your code here
});
Simple enough. Until #container
doesn’t exist when that code runs. Maybe the element gets injected later by an AJAX call, or maybe your framework renders it on some future user interaction. In these cases, the handler is useless. Nothing happens.
The Elegant Solution
Later came this beauty:
$(document).on('click', '#container', function() {
console.log('Container clicked!');
});
on()
doesn’t attach the handler directly to #container
. It attaches it to the document — a stable parent that’s always there — and then filters click events as they bubble up.
That means even if the button doesn’t exist yet (say it’s injected later via AJAX, or rendered dynamically by a framework), the event handler still works. The binding outlives the element itself.
In other words, on()
doesn’t wait for the DOM to stabilize. It says, “I’ll catch it when it appears.”
The pattern behind it — delegated event handling — is still one of the most efficient ways to manage dynamic UIs. It:
- You don’t need to re-run initialization code when you inject HTML.
- You don’t need to “rehydrate” or “re-bind” after server-side rendering or AJAX updates.
- Your event layer can be a single, stable surface that observes the page instead of a thousand tiny scripts that attach and detach handlers as nodes appear and disappear.
That’s not just minor convenience. It changes how you think about building UIs. Instead of fighting the DOM’s mutability, you accept it and rely on the browser’s native bubbling to keep your logic connected.
Why This Should’ve Changed the Game
Imagine if more libraries had built around that. Instead of constantly re-binding or re-initializing event handlers every time the DOM changed, you’d have a single, persistent layer observing all interactive behavior through bubbling.
That’s a simpler, more universal way to handle dynamic UIs — no frameworks, no hydration, no re-renders. Just stable delegation logic that works with any HTML injection or template system.
How React Complicated What Was Already Simple
In jQuery, you attach a handler once and forget about it. In React, the same act turns into a mini philosophical ordeal.
Let’s say you have this:
function App() {
const handleClick = () => {
console.log('Button clicked');
};
return <button onClick={handleClick}>Click me</button>;
}
Looks innocent, right? Except React doesn’t just attach that once.
Every time this component re-renders, React recreates handleClick
.
It’s a new function in memory — different identity, same behavior.
So when React compares virtual DOM nodes, it sees a “changed” handler,
decides it needs to update the element, and does more work than necessary.
That’s not jQuery’s problem. jQuery delegates. React rebinds. Constantly.
And then React realized, “Wait, maybe that’s not great for performance.”
So it introduced useCallback()
:
function App() {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <button onClick={handleClick}>Click me</button>;
}
Now developers get to “opt out” of the inefficiency React itself created. You tell React, “Please don’t rebuild this function every time.” And React says, “Excellent thinking! You’re optimizing your app!” Like congratulating you for fixing a hole it dug in the first place.
Meanwhile, jQuery’s on()
was sitting there years ago,
quietly delegating events through bubbling —
never re-creating handlers, never pretending that was innovation.
The Illusion of “Component Encapsulation”
React didn’t stop at recreating handlers; it fenced them in.
Every event in React lives inside its component bubble — what it calls the synthetic event system.
That means the click on your <button>
isn’t a real browser click in the DOM sense;
it’s React’s imitation of one, wrapped in layers of abstraction so React can control when and how it runs.
The side effect? Handlers in React don’t exist globally or delegate naturally through the DOM. They only work inside the virtual tree React manages. So if you dynamically add an element outside React’s scope — or inject HTML that React didn’t render — none of your bindings apply. React simply doesn’t see it.
Final Thoughts
jQuery.on()
wasn’t just a handy shortcut. It was a smart design choice — let the browser handle events once, listen globally, and simply check what triggered them. If developers had really understood that back then, we probably would’ve ended up with smaller, faster libraries built around delegation and context instead of all this constant re-binding and rehydration work.