A ReactJS + Redux crash course
React reshaped the front end around components and one-way data flow; Redux added predictable state management. A deep-dive on both for a Rails developer — components, props and state, the Redux loop — and when Redux is overkill.
React has become the default way to build genuinely application-like front ends, and for a Rails developer arriving from server-rendered views (and maybe an AngularJS detour) it is worth understanding properly — both the component model that makes React click and the Redux pattern that so often rides alongside it. Here is a crash course in both, with an honest take on when Redux earns its place and when it is ceremony.
React: UI as a function of state
The core idea of React is simple and powerful: your UI is a function of state. You describe what the interface should look like for a given set of data, and when the data changes, React figures out the minimal DOM updates to match. You never imperatively poke the DOM (“set this text, toggle that class”) the way you did with jQuery; you change the state and declare the result.
The unit of construction is the component — a self-contained piece of UI that takes inputs (props) and returns markup (JSX). Components compose into trees, small ones building large ones:
function ProductCard({ product, onAdd }) {
return (
<div className="card">
<h3>{product.name}</h3>
<span className="price">{formatPrice(product.price)}</span>
<button onClick={() => onAdd(product.id)}>Add to cart</button>
</div>
)
}
function Catalog({ products, onAdd }) {
return products.map(p => <ProductCard key={p.id} product={p} onAdd={onAdd} />)
}
Two principles define how data moves. Props flow down: a parent passes data to children, and
children cannot change their props — they are read-only inputs. Events flow up: when something
happens in a child, it calls a function the parent gave it (onAdd), so the parent owns the
change. This one-way data flow is the heart of React and the thing that makes large UIs
tractable: data has one direction, so when something is wrong you trace it along a single path,
not through the tangle of two-way bindings that made AngularJS apps hard to reason about at scale.
State, and where it lives
Props are inputs from above; state is data a component owns itself. As of React 16.8 (just
released), the modern way to hold state in a function component is the useState hook:
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
The critical discipline is where state lives. State should live at the lowest common ancestor of the components that need it, and you “lift it up” only as far as necessary. A counter’s state belongs in the counter; a shopping cart shared across many components belongs higher up. Get this placement right and most apps need nothing more than component state plus props — which is the setup to the Redux question.
Redux: a single, predictable store
As an app grows, some state is needed all over the tree — the logged-in user, the cart, app-wide settings — and threading it through props across many layers (“prop drilling”) becomes painful. Redux is the established answer: pull that shared state out of the component tree into a single store, with a strict, predictable pattern for changing it.
The Redux loop has three parts. Actions are plain objects describing what happened. Reducers are pure functions that take the current state and an action and return the new state. The store holds the state and runs the reducers. The flow is rigidly one-directional:
// action — describes what happened
{ type: "CART_ADD", productId: 42 }
// reducer — pure: (state, action) => new state, no mutation
function cart(state = [], action) {
switch (action.type) {
case "CART_ADD": return [...state, action.productId]
case "CART_REMOVE": return state.filter(id => id !== action.productId)
default: return state
}
}
// a component dispatches an action; the store updates; subscribed components re-render
dispatch({ type: "CART_ADD", productId: 42 })
The discipline is what gives Redux its value: state is changed only by dispatching an action through a pure reducer that never mutates but returns new state. Because every change is an explicit, logged action and every reducer is deterministic, the whole app’s state becomes predictable and traceable — you can see exactly what changed, when, and why (the Redux DevTools even let you replay actions and time-travel). For a complex app with lots of shared, interacting state, that predictability is genuinely valuable.
When Redux is overkill
Here is the honest part, and it is the same judgment that runs through all of these posts: most apps reach for Redux too early. Redux adds real boilerplate — actions, reducers, the store, wiring components to it — and that ceremony only pays off when you genuinely have complex, widely-shared state. For a great many apps, plain component state plus passing props (and, for the prop-drilling problem, React’s built-in Context for a few global values) is entirely sufficient and far simpler.
The signals that you actually need Redux: state that many distant components read and write, complex state interactions that are hard to follow, or a real need to trace and debug state changes over time. The signals that you do not: a handful of forms and some local UI state, which component state handles fine. Adopt Redux when the predictability it buys is worth the boilerplate it costs — and not before, because Redux-everything is a classic way to make a simple app needlessly complicated.
For a Rails developer
The mental shift from server-rendered Rails is the big one: in Rails the server owns state and renders HTML; in React the client owns UI state and renders itself from it, talking to your Rails app as a JSON API. Reach for React when the front end is genuinely application-like (rich, stateful, interactive) — and remember the earlier post’s point that for content-and-forms apps, Stimulus over server-rendered views is often the better, lighter fit. React is the right tool for real client applications, not for sprinkling interactivity on a mostly-server-rendered site.
Verdict
React’s model — UI as a function of state, built from composable components, with props flowing down and events flowing up — is a genuinely better way to build complex front ends than the imperative jQuery or two-way-bound AngularJS that came before, because one-way data flow keeps large UIs traceable. Redux extends that predictability to app-wide shared state through its strict action/reducer/store loop — but it is boilerplate you should add only when complex shared state genuinely needs it, not reflexively. Learn the component model first and lean on local state and props; introduce Redux when the app’s state truly outgrows them. Used with that judgment, React and Redux are excellent for real client applications — and the discipline of knowing when not to reach for them is as valuable as knowing how.