Open a jQuery codebase from 2012 and try to answer one question: where is the cart count stored?
Maybe it's a JavaScript variable. Maybe it's the text inside <span class="cart-badge">. Maybe it's a data-count attribute on a button. Often, it's all three, and they disagree.
That disagreement is the problem React, Redux, and every state library after them were built to solve. Here is how it looked before them.
The simplest case: a counter
Plain JavaScript:
<h2 id="count">0</h2>
<button id="increment">Increment</button>
<script>
let count = 0;
document.getElementById("increment").addEventListener("click", () => {
count++;
document.getElementById("count").textContent = count;
});
</script>
jQuery (2006 onward):
<h2 id="count">0</h2>
<button id="increment">Increment</button>
<script>
let count = 0;
$("#increment").on("click", function () {
count++;
$("#count").text(count);
});
</script>
React:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
}
jQuery made selecting elements and wiring events much nicer, but look at what it did not change: you still update the variable and you still tell the DOM what to do. Two steps, both manual.
React removes the second step. You change the state, and React works out the DOM changes.
The difference is imperative (do these DOM operations) versus declarative (here is what the UI looks like for this state).
Where it breaks: the cart
A counter has one number and one place that shows it. Real apps don't.
Think of a shop with a navbar badge, a product page, a cart page, and a checkout summary. Adding an item has to update all of them:
let cart = [];
function addToCart(product) {
cart.push(product);
$(".cart-badge").text(cart.length);
$("#cart-total").text(calculateTotal(cart));
$("#product-" + product.id + " .add-btn").text("In cart");
if (cart.length === 1) {
$("#empty-cart-message").hide();
}
// ...and the checkout summary? And the "remove" flow?
}
This is an illustrative example, not code from a specific project, but the shape is very familiar. Every feature that touches the cart needs to remember every place the cart is displayed. Miss one and the UI quietly lies to the user.
Two common failure modes:
- Forgotten update: You add a "remove" button and forget to refresh the badge.
-
DOM as the database: Someone reads
parseInt($(".cart-badge").text())because it was convenient. Now the display is the source of truth, and formatting changes like "1 item" can break logic.
flowchart TD
A[User clicks Add to cart] --> B[Update cart variable]
B --> C[Update navbar badge]
B --> D[Update product button]
B --> E[Update cart total]
B --> F[Update checkout summary]
F -. forgot this one .-> G[UI out of sync]
How the tools evolved
The eras overlap, and none of them fully disappeared, but roughly:
| Era | Approach | What it helped with | What was still manual |
|---|---|---|---|
| Static HTML (1990s) | Reload the page from the server | Nothing to sync | Any interactivity |
| Vanilla JS (late 1990s to 2000s) | Variables + DOM APIs | Dynamic pages | Selecting and updating everything |
| jQuery (2006) | Selectors, events, simpler DOM | Cross-browser pain, less code | State-to-DOM sync |
| Backbone / AngularJS (about 2010) | Models, views, bindings | Structure, some automatic syncing | Complexity, tricky data flow |
| React (2013) | Components render from state | DOM updates | Sharing state across components |
| Redux, MobX, Context, Zustand (2015 onward) | Centralized or specialized stores | Shared state at scale | Choosing the right tool |
Backbone gave you models that fire change events, but you still wrote the render logic. AngularJS added two-way binding, which was convenient and also made it harder to see which change caused which update.
Then React's pitch was simple: stop writing the update step. Describe the UI as a function of state.
The same cart, declaratively
function Navbar({ cart }) {
return <span className="cart-badge">{cart.length}</span>;
}
function CartSummary({ cart }) {
const total = cart.reduce((sum, item) => sum + item.price, 0);
return <p>Total: {total}</p>;
}
function App() {
const [cart, setCart] = useState([]);
const addToCart = (product) => setCart((prev) => [...prev, product]);
return (
<>
<Navbar cart={cart} />
<ProductList onAdd={addToCart} />
<CartSummary cart={cart} />
</>
);
}
There is no "update the badge" code. The badge is cart.length, always.
That solves rendering. It doesn't solve sharing: once many distant components need cart, passing props through layers gets awkward. That is the gap Redux filled in 2015 with a single store and explicit updates:
// Redux Toolkit
const cartSlice = createSlice({
name: "cart",
initialState: [],
reducers: {
added: (state, action) => {
state.push(action.payload);
},
},
});
Whether you need Redux, Context, or Zustand is a separate question. Plenty of apps are fine with plain React state.
What to take from this
- Old-style bugs came from duplicated state: a variable, the DOM, and sometimes an attribute, all claiming to be the truth.
- React's biggest change was not "components." It was one source of truth that the UI is derived from.
- State management libraries solve sharing and coordinating state, not the basic rendering problem.
- You can still see this today: any time you read data back out of the DOM in React (
ref.current.valueeverywhere,document.querySelector), you are partly back in the old model.
If an interviewer asks
Before React, state lived in JavaScript variables, globals, browser storage, and often the DOM itself. jQuery simplified DOM updates, and MVC frameworks like Backbone and AngularJS added structure and bindings. As apps grew, keeping data and UI consistent got hard. React introduced a declarative, component-based model where UI is derived from state, and libraries like Redux later made shared state more predictable.
And one distinction worth making: state existed long before React. React changed how state connects to the UI.
Your turn
If you worked in the jQuery or Backbone days, what was the worst "UI out of sync" bug you remember? And if you started with React, have you ever caught yourself reading values back from the DOM instead of state?
Top comments (0)