Real-world examples of managing global state with Redux in React

If you’re trying to understand Redux beyond the usual counter demo, you need real examples of managing global state with Redux in React that look like actual production code. In 2024, React apps are rarely simple: you’re juggling authentication, feature flags, server cache, WebSocket updates, and user preferences across dozens of components. That’s exactly where Redux still shines. In this guide, we’ll walk through practical examples of managing global state with Redux in React, from authentication flows to complex dashboards. We’ll use modern Redux Toolkit patterns, show how these patterns scale, and point out where Redux is worth the overhead and where it isn’t. If you’ve only seen a to‑do list example of Redux, this article should reset your expectations. We’ll also touch on current trends like RTK Query, code splitting, and type‑safe state in large teams. By the end, you’ll have a clear mental model and several reusable patterns you can apply directly to your own React codebase.
Written by
Jamie
Published

Examples of managing global state with Redux in React (beyond counters)

Most developers first meet Redux through a toy counter. That’s fine for teaching actions and reducers, but it’s a terrible reflection of how Redux is used in real apps. Let’s walk through real examples of managing global state with Redux in React that you actually see in production.

All examples use Redux Toolkit (RTK), which is the officially recommended way to write Redux logic as of 2024.

npm install @reduxjs/toolkit react-redux

Example of auth and user session state with Redux

Authentication is one of the most common examples of managing global state with Redux in React. Multiple parts of the app need to know if the user is logged in, what their role is, and whether their token is still valid.

// store/authSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';

interface User {
  id: string;
  email: string;
  role: 'user' | 'admin';
}

interface AuthState {
  user: User | null;
  accessToken: string | null;
  status: 'idle' | 'loading' | 'authenticated' | 'error';
  error?: string;
}

const initialState: AuthState = {
  user: null,
  accessToken: null,
  status: 'idle',
};

export const login = createAsyncThunk(
  'auth/login',
  async (credentials: { email: string; password: string }) => {
    const res = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(credentials),
    });
    if (!res.ok) throw new Error('Login failed');
    return (await res.json()) as { user: User; accessToken: string };
  }
);

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    logout(state) {
      state.user = null;
      state.accessToken = null;
      state.status = 'idle';
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(login.pending, (state) => {
        state.status = 'loading';
        state.error = undefined;
      })
      .addCase(login.fulfilled, (state, action) => {
        state.status = 'authenticated';
        state.user = action.payload.user;
        state.accessToken = action.payload.accessToken;
      })
      .addCase(login.rejected, (state, action) => {
        state.status = 'error';
        state.error = action.error.message;
      });
  },
});

export const { logout } = authSlice.actions;
export default authSlice.reducer;

Now any component can subscribe to this global auth state:

// components/NavBar.tsx
import { useSelector, useDispatch } from 'react-redux';
import type { RootState } from '../store';
import { logout } from '../store/authSlice';

export function NavBar() {
  const dispatch = useDispatch();
  const user = useSelector((state: RootState) => state.auth.user);

  return (
    <header>
      <h1>Dashboard</h1>
      {user ? (
        <div>
          <span>Signed in as {user.email}</span>
          <button onClick={() => dispatch(logout())}>Log out</button>
        </div>
      ) : (
        <a href="/login">Log in</a>
      )}
    </header>
  );
}

This is one of the best examples of managing global state with Redux in React because it prevents prop drilling and keeps login logic centralized.


Examples include feature flags and A/B testing state

Modern product teams often roll out features gradually. That means you need global feature flag state that multiple components can read consistently. This is another practical example of managing global state with Redux in React.

// store/featureFlagsSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export type FlagKey = 'newDashboard' | 'betaSearch' | 'darkMode';

interface FeatureFlagsState {
  flags: Record<FlagKey, boolean>;
  loaded: boolean;
}

const initialState: FeatureFlagsState = {
  flags: { newDashboard: false, betaSearch: false, darkMode: false },
  loaded: false,
};

export const fetchFeatureFlags = createAsyncThunk(
  'flags/fetch',
  async () => {
    const res = await fetch('/api/feature-flags');
    return (await res.json()) as Partial<Record<FlagKey, boolean>>;
  }
);

const featureFlagsSlice = createSlice({
  name: 'featureFlags',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchFeatureFlags.fulfilled, (state, action) => {
      state.flags = { ...state.flags, ...action.payload };
      state.loaded = true;
    });
  },
});

export default featureFlagsSlice.reducer;

Components can now toggle behavior based on these flags without having to pass booleans through every intermediate component.


Global UI state: modals, toasts, and layout

Another real example of managing global state with Redux in React is UI chrome: modals, drawers, snackbars, and global spinners. These are not “business” data, but they are shared across the app.

// store/uiSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface Toast {
  id: string;
  message: string;
  type: 'success' | 'error' | 'info';
}

interface UiState {
  activeModal: string | null;
  toasts: Toast[];
}

const initialState: UiState = {
  activeModal: null,
  toasts: [],
};

const uiSlice = createSlice({
  name: 'ui',
  initialState,
  reducers: {
    openModal(state, action: PayloadAction<string>) {
      state.activeModal = action.payload;
    },
    closeModal(state) {
      state.activeModal = null;
    },
    pushToast(state, action: PayloadAction<Omit<Toast, 'id'>>) {
      const id = crypto.randomUUID();
      state.toasts.push({ id, ...action.payload });
    },
    removeToast(state, action: PayloadAction<string>) {
      state.toasts = state.toasts.filter((t) => t.id !== action.payload);
    },
  },
});

export const { openModal, closeModal, pushToast, removeToast } = uiSlice.actions;
export default uiSlice.reducer;

A top-level <ToastContainer /> and <ModalRoot /> can subscribe to this slice and render UI consistently, while any feature module can dispatch pushToast or openModal.


Data fetching in 2024: RTK Query as global server cache

Redux Toolkit’s RTK Query has become one of the best examples of managing global state with Redux in React when your “state” is really a client-side cache of server data.

Instead of writing reducers by hand, you declare endpoints and let RTK Query manage caching, loading flags, and invalidation.

// store/api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Project', 'User'],
  endpoints: (builder) => ({
    getProjects: builder.query<Project[], void>({
      query: () => '/projects',
      providesTags: ['Project'],
    }),
    updateProject: builder.mutation<Project, { id: string; data: Partial<Project>}>({
      query: ({ id, data }) => ({
        url: `/projects/${id}`,
        method: 'PATCH',
        body: data,
      }),
      invalidatesTags: ['Project'],
    }),
  }),
});

export const { useGetProjectsQuery, useUpdateProjectMutation } = api;

This turns your Redux store into a normalized, shared cache of server data that every component can read. It’s a modern, production-grade example of managing global state with Redux in React without hand-rolling thunks and reducers for every endpoint.

For deeper background on client-side state vs. server state, the React team’s docs on state management patterns are worth a read: https://react.dev/learn/managing-state.


Cross-tab sync: global state across browser tabs

Some apps need state to be consistent even when the user opens multiple tabs. Think of a trading app where logging out in one tab should log you out everywhere.

Redux plays well with the BroadcastChannel API or localStorage events. This is a more advanced example of managing global state with Redux in React, but it’s common in finance and healthcare dashboards.

// syncMiddleware.ts
import type { Middleware } from '@reduxjs/toolkit';

const channel = new BroadcastChannel('redux-sync');

export const syncMiddleware: Middleware = (store) => {
  channel.onmessage = (event) => {
    const { type, payload } = event.data;
    store.dispatch({ type, payload, meta: { fromBroadcast: true } });
  };

  return (next) => (action) => {
    const result = next(action);

    if (!action.meta?.fromBroadcast && action.type.startsWith('auth/')) {
      channel.postMessage({ type: action.type, payload: action.payload });
    }

    return result;
  };
};

Now your auth slice actions propagate between tabs. This is one of those real examples of managing global state with Redux in React that you rarely see in tutorials but often see in production.


Form wizards and multi-step flows

Multi-step onboarding flows are another example of managing global state with Redux in React. Each step might be its own route, but they all contribute to the same final payload.

// store/onboardingSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface OnboardingState {
  step: 1 | 2 | 3;
  basicInfo: { name: string; company: string } | null;
  billingInfo: { cardLast4: string } | null;
  preferences: { newsletter: boolean } | null;
}

const initialState: OnboardingState = {
  step: 1,
  basicInfo: null,
  billingInfo: null,
  preferences: null,
};

const onboardingSlice = createSlice({
  name: 'onboarding',
  initialState,
  reducers: {
    saveBasicInfo(state, action: PayloadAction<OnboardingState['basicInfo']>) {
      state.basicInfo = action.payload;
      state.step = 2;
    },
    saveBillingInfo(state, action: PayloadAction<OnboardingState['billingInfo']>) {
      state.billingInfo = action.payload;
      state.step = 3;
    },
    savePreferences(state, action: PayloadAction<OnboardingState['preferences']>) {
      state.preferences = action.payload;
    },
    resetOnboarding() {
      return initialState;
    },
  },
});

export const { saveBasicInfo, saveBillingInfo, savePreferences, resetOnboarding } = onboardingSlice.actions;
export default onboardingSlice.reducer;

Each step component reads from and writes to this shared slice, so the user can navigate back and forth without losing data.


In 2024 and heading into 2025, you see a few patterns in how teams use Redux in large React apps:

  • Redux Toolkit everywhere. Hand-written switch reducers are basically legacy code now. RTK’s createSlice and createAsyncThunk are the standard.
  • RTK Query or alternative server-state libraries. Many teams pair Redux with RTK Query, React Query, or TanStack Query. When they choose RTK Query, it becomes one of the clearest examples of managing global state with Redux in React because the store is the single source of truth for cached API data.
  • TypeScript by default. Strongly typed slices and selectors reduce runtime bugs. This is especially visible in fintech, health tech, and government apps where correctness matters.
  • Code splitting. Large apps dynamically inject reducers when routes load to keep the initial bundle smaller.

For a broader discussion of state management tradeoffs in large front-end systems, the Software Engineering Institute at Carnegie Mellon has relevant material on scalable software design: https://resources.sei.cmu.edu/.


Combining Redux with local component state

Not everything belongs in Redux. One of the best examples of managing global state with Redux in React is knowing what not to globalize.

Good candidates for Redux:

  • Auth and user profile
  • Permissions and roles
  • Feature flags
  • Shared UI chrome (modals, toasts)
  • Cross-route data (wizard flows, shopping carts)
  • Cached server data shared across many screens

Better kept in local component state:

  • Input values for a simple form
  • Temporary UI toggles inside a single component
  • Hover/focus states

The React docs emphasize this gradient between local and shared state rather than treating Redux as a default for everything: https://react.dev/learn/choosing-the-state-structure.


Putting it together: wiring the store

Here’s a minimal example of managing global state with Redux in React using several slices at once.

// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import authReducer from './authSlice';
import uiReducer from './uiSlice';
import featureFlagsReducer from './featureFlagsSlice';
import onboardingReducer from './onboardingSlice';
import { api } from './api';
import { syncMiddleware } from './syncMiddleware';

export const store = configureStore({
  reducer: {
    auth: authReducer,
    ui: uiReducer,
    featureFlags: featureFlagsReducer,
    onboarding: onboardingReducer,
    [api.reducerPath]: api.reducer,
  },
  middleware: (getDefault) =>
    getDefault().concat(api.middleware, syncMiddleware),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

And the React integration:

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

At this point, you have several concrete examples of managing global state with Redux in React: auth, feature flags, global UI, onboarding flows, and server cache, all wired into a single store with modern tooling.


FAQ: examples of managing global state with Redux in React

Q: What are some real examples of managing global state with Redux in React?
Common real-world examples include authentication and user sessions, permissions, feature flags, global modals and toasts, cross-route form wizards, shopping carts, and shared server data fetched via RTK Query.

Q: When should I choose Redux over React Context?
Context works well for simple, low-frequency updates like theme or locale. Redux is a better fit when you have frequent updates, complex logic, time-travel debugging needs, or when many distant components depend on the same data. Redux DevTools and middleware support also make it easier to audit behavior in regulated domains like healthcare and finance. For general guidance on building reliable software systems, the NIST Software Assurance resources are helpful: https://samate.nist.gov/.

Q: Is Redux still relevant in 2024–2025 with hooks and server components?
Yes, especially for large, client-heavy applications. Hooks, Context, and Server Components solve different layers of the problem. Redux remains valuable when you need predictable, centralized client-side state with strong tooling, logging, and cross-cutting middleware.

Q: Can I use Redux only for server data and keep local UI state elsewhere?
Yes, and many teams do exactly that using RTK Query. In that setup, Redux becomes your shared cache for server data, while local component state (or lightweight context) handles purely presentational concerns.

Q: What is a simple example of migrating from legacy Redux to Redux Toolkit?
Start by replacing one legacy reducer with a createSlice, wiring it into the existing store. Then migrate thunks to createAsyncThunk. This incremental approach lets you keep the current behavior while modernizing your codebase.

Explore More React Code Snippets

Discover more examples and insights in this category.

View All React Code Snippets