
The Role of Redux in React.js Development

Redux is one of those tools that engineers either swear by or quietly resent. If you've been building React applications of any meaningful scale, you've almost certainly had the conversation about whether to use it. This post covers what Redux actually does, where it earns its keep, and where you're better off without it.
What problem does Redux actually solve?
React's built-in state model is component-local. useState and useReducer live inside a component and flow downward through props. That works fine until you have state that multiple, unrelated components need to read or write.
The naive fix is prop drilling — passing state through five layers of components that don't care about it, just to get it to the one that does. useContext is the next step, and it's a reasonable one for low-frequency updates. But React context re-renders every subscriber when any part of the context value changes. For high-frequency state like form inputs or live data feeds, that becomes a performance problem quickly.
Redux solves this by putting state in a single store outside the React tree. Any component can read from or write to the store without coupling to its parent. The state shape is explicit, updates happen through defined actions, and the entire state history is inspectable.
That last point matters more than people give it credit for. Being able to replay a sequence of actions and reproduce a bug exactly is a significant debugging advantage in complex UIs.
How Redux actually works (and what's changed since v1)
The core model is unchanged since Dan Abramov introduced Redux in 2015: a single store holds your application state, actions are plain objects that describe what happened, and reducers are pure functions that take the current state plus an action and return the next state.
What has changed substantially is the ergonomics. Writing Redux before Redux Toolkit (RTK) meant a lot of boilerplate: separate files for action types, action creators, and reducers, all kept in sync manually. RTK, which became the official recommended approach from Redux 4.x onwards, collapses that. createSlice generates action creators and action types from your reducer logic. createAsyncThunk handles async flows without middleware gymnastics. The RTK Query layer adds data fetching and caching on top of that.
The mental model underneath RTK is still the original: immutable updates, pure reducers, unidirectional data flow. RTK just removes the ceremony.
// RTK slice — compare this to the pre-RTK equivalent
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 },
decrement: state => { state.value -= 1 },
},
});
The state.value += 1 line looks like a mutation, but RTK uses Immer internally, so the reducer stays pure. It's worth understanding that, because it trips people up when they try to use RTK patterns outside of RTK.
When should you actually use Redux?
The honest answer: not as often as people default to it.
For a simple CRUD app or a dashboard with self-contained data per page, useState and React Query (or RTK Query) get you further with less overhead. Redux shines in specific conditions.
| Condition | Redux adds value? |
|---|---|
| Many unrelated components sharing the same state | Yes |
| Complex, multi-step user flows with branching logic | Yes |
| Need for time-travel debugging or state snapshots | Yes |
| Server state (API data, caching, loading flags) | RTK Query or React Query instead |
| Simple local form state | No |
| Single-page with few shared state needs | No |
A good rule of thumb: if you can describe your state management problem in one sentence, you probably don't need Redux.
/// Not sure where to start?
Get the architecture before you commit
Tell us what you're building and we'll map the technical approach, stack, and rough timeline. No cost, no obligation, no sales call required.
What are the real trade-offs compared to Zustand, Jotai, and Context?
Redux is not the only option in 2025, and the alternatives are mature.
Zustand (currently at v4) is a minimal store with a hook-based API. There's no action/reducer pattern — you mutate state directly through setter functions. The bundle size is around 1kB compared to Redux Toolkit's ~10kB. For small-to-medium apps, Zustand is often the right call.
Jotai takes an atomic approach inspired by Recoil. State is split into atoms that components subscribe to individually. This avoids the re-render problem you get with context, and it's fine-grained by design. It fits well when your state is naturally decomposable into independent units.
Recoil (Meta's library) offers a similar atomic model but has had slow maintenance velocity. Hard to recommend for new projects.
MobX is reactive rather than functional. It observes state objects and re-renders components that accessed the changed properties. The pattern is very different from Redux, and it works well for domain-heavy applications where you're modelling entities with behaviour.
The pattern Redux uniquely enforces is explicit, traceable state transitions. If you're building something where auditability matters (financial UIs, complex workflows, anything that needs undo/redo), that structure is worth the overhead. If you just need shared state and don't care about the audit trail, Zustand is simpler.
How does Redux interact with server state and data fetching?
This is where a lot of Redux setups go wrong. Teams often put API response data directly into Redux slices, then build their own loading/error/cache logic on top. That's reinventing what RTK Query or React Query already do well.
Server state has different characteristics from client state. It's asynchronous, it can go stale, it needs cache invalidation and background refetching. Redux's synchronous, predictable model isn't a natural fit for those requirements.
RTK Query, built into Redux Toolkit, handles this explicitly. It generates hooks for each endpoint, manages cache lifetimes, handles optimistic updates, and invalidates related cache entries automatically. If you're already using Redux, RTK Query is the right way to handle server data. If you're not using Redux, React Query 5 does the same job without needing Redux at all.
The practical split that works well at scale: Redux (or Zustand) for client state (UI state, selected items, multi-step form progress), and RTK Query or React Query for server state.
Conclusion
Redux is a precise tool with a specific use case. Large applications with genuinely complex shared client state, where traceability and predictable updates matter, benefit from it. For most applications that aren't in that category, the overhead doesn't pay off.
If you're evaluating your current React architecture, start by separating server state from client state. Reach for React Query or RTK Query for the former. Then assess whether your client state is complex enough to warrant Redux or whether Zustand covers it.
The next concrete step: audit your existing Redux store and count how many slices are holding API response data. Those are the candidates to migrate to RTK Query first. The remaining slices are where Redux is actually doing its intended job.
FAQ
Is Redux still relevant in 2025? Yes, but its role has narrowed. RTK Query and React Query handle server state better than hand-rolled Redux slices ever did. Redux remains the right choice for complex client-side state with strict traceability requirements. For simpler apps, Zustand or Jotai are more appropriate and involve far less configuration.
What is the difference between Redux Toolkit and plain Redux?
Plain Redux requires you to write action type constants, action creator functions, and reducers separately and keep them manually in sync. Redux Toolkit's createSlice generates all of that from a single object. RTK also bundles Immer for immutable updates and includes RTK Query for data fetching. The underlying store model is identical.
Does Redux work with React Server Components? No, not directly. Redux relies on the React context API and client-side hooks, both of which are unavailable in React Server Components. If you're building a Next.js 14+ application using the App Router with RSC, Redux state lives in client components only. This is a real architectural constraint to account for upfront.
When should I use useContext instead of Redux?
Use useContext when the state changes infrequently and the number of subscribers is small — think theme, locale, or authenticated user data. The problem with context for high-frequency state is that every component consuming the context re-renders on any change. Redux (and Zustand) use subscription-based selectors that only re-render components when the specific slice of state they read actually changes.
How large is the Redux bundle size impact? Redux core is around 2kB minified and gzipped. Redux Toolkit adds roughly 8–10kB on top of that (including Immer and RTK Query). Zustand, by comparison, is approximately 1kB. If bundle size is a primary concern — mobile web, low-bandwidth markets — that difference is worth weighing against the features Redux provides.
Have a project in mind? Contact Sodio Technologies to discuss your requirements and explore the right technology solution for your business.
/// Work with us
Talk to the engineers who'd build it
You'll get a technical scope, timeline and cost estimate from the people doing the work, not an account manager. In-house team, no subcontracting, since 2016.
