Skip to content

Taming Redux in a large codebase (and knowing when to drop it)

· 4 min read · Amrith Vengalath

  • React Native
  • Redux
  • Architecture

Redux has a reputation for being heavy and ceremony-laden, and a lot of that reputation is earned by codebases that used it badly rather than by Redux itself. I've worked in large React Native apps where Redux was a pleasure and others where it was a swamp. The difference was never the library. It was discipline about what went into the store and how much boilerplate the team tolerated.

Here's what disciplined Redux looks like, and the part nobody likes to say out loud: when not to use it at all.

The original sin: putting everything in the store

The most common Redux mess I've seen is treating the store as a dumping ground for all state. Form inputs, toggle states, the data from the last API call, UI flags, server data - all of it, in global state, with actions and reducers for each.

Most of that doesn't belong there. The useful split:

  • Server state - data fetched from an API. This often doesn't belong in Redux at all; a data-fetching library handles caching, refetching, and invalidation far better than reducers you wrote by hand.
  • Genuinely global client state - the authenticated user, app-wide settings, a cart that many screens touch. This is what Redux is actually good at.
  • Local UI state - is this dropdown open, what's in this text field. This is useState. Putting it in Redux is how you get a thousand-action store and dread.

A lot of "Redux is too much" complaints evaporate once you stop putting the wrong things in it.

Redux Toolkit fixes the boilerplate

The old Redux - hand-written action types, action creators, switch-statement reducers, immutable update spread-gymnastics - was genuinely too much typing. Redux Toolkit is the official answer and it's not optional in my book anymore. A slice replaces all of that:

import { createSlice } from "@reduxjs/toolkit";
 
const cartSlice = createSlice({
  name: "cart",
  initialState: { items: [] },
  reducers: {
    addItem(state, action) {
      // looks like mutation, is actually safe - Immer handles immutability
      state.items.push(action.payload);
    },
    removeItem(state, action) {
      state.items = state.items.filter((i) => i.id !== action.payload);
    },
  },
});
 
export const { addItem, removeItem } = cartSlice.actions;
export default cartSlice.reducer;

No action-type constants, no separate action creators, no manual immutable updates - Immer lets you write what looks like mutation while keeping the state immutable underneath. The amount of code this deletes from an old Redux codebase is the single biggest improvement you can make to one.

Selectors and structure that scale

Two habits that keep a large store sane:

  • Select narrowly. Components subscribe to the smallest slice of state they need, through selectors, so they don't re-render when unrelated state changes. A component reading the whole store re-renders on every action, which is a quiet performance leak in big apps.
  • Organize by feature, not by type. One folder per feature with its slice, selectors, and logic together, rather than a giant reducers folder and a giant actions folder. When you work on the cart, everything cart is in one place.

The honest question: do you need Redux at all?

This is the part Redux fans skip. A lot of apps don't need it. If your global client state is small - a user object, some settings - React Context plus a data-fetching library for server state covers it, with less machinery. Redux earns its place when you have substantial, complex, frequently-updated global client state that many parts of the app read and write, where the predictability and devtools are worth the overhead.

On smaller apps I've happily skipped it. On a large one with a complex shared cart, offline queue, and app-wide state touched everywhere, Redux Toolkit was the right call and I didn't resent it once. The mistake is reaching for it reflexively because it's what you know, or avoiding it dogmatically because of a reputation it mostly earned in its pre-Toolkit days.

The summary

Redux isn't heavy; misused Redux is heavy. Keep server state out of it, use Redux Toolkit so the boilerplate stops being a reason to complain, select narrowly, organize by feature - and be honest about whether your app's state is complex enough to need it in the first place. Used with that discipline, it's a calm, debuggable foundation. Used as a catch-all, it's exactly the swamp its critics describe.