mcpField
R
redux-toolkit

redux-toolkit

github.com/claude-dev-suiteDevelopment4642 installsUpdated 4d ago
Repository

About redux-toolkit

A Redux Toolkit skill guide for developers setting up state management in Redux applications. It covers slices, store configuration, async thunks, typed hooks, RTK Query, and practical guidance for testing and error handling.

Key features

  1. 01Create slices and configure stores
  2. 02Handle async actions with thunks
  3. 03Use typed React Redux hooks
  4. 04Fetch data with RTK Query
  5. 05Apply state management best practices
  6. 06Troubleshoot common Redux issues

Use cases

  1. 01Set up typed application state
  2. 02Implement asynchronous user requests
  3. 03Manage API data and cache invalidation
  4. 04Diagnose Redux state and selector issues

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill redux-toolkit

After installing, type/redux-toolkitin Claude Code to confirm it loaded.

Bundled filesquick-ref/rtk-query.mdquick-ref/slices.md

Details

From the project README

Redux Toolkit Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: redux-toolkit for comprehensive documentation.

Slice

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CounterState {
  value: number;
}

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 } as CounterState,
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

Store Setup

import { configureStore } from '@reduxjs/toolkit';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    users: usersReducer,
  },
});

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

Async Thunks

import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async (_, { rejectWithValue }) => {
    try {
      const response = await api.getUsers();
      return response.data;
    } catch (err) {
      return rejectWithValue(err.message);
    }
  }
);

// In slice
extraReducers: (builder) => {
  builder
    .addCase(fetchUsers.pending, (state) => { state.loading = true; })
    .addCase(fetchUsers.fulfilled, (state, action) => {
      state.loading = false;
      state.users = action.payload;
    })
    .addCase(fetchUsers.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload;
    });
}

Hooks

import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from './store';

export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();

// Usage
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
dispatch(increment());

When NOT to Use This Skill

ScenarioUse Instead
Simple global state (user prefs, theme)zustand for less boilerplate
Server state managementtanstack-query or RTK Query only
Vue 3 applicationspinia
Small projects or prototypesReact Context + hooks or Zustand
Component-local stateReact useState/useReducer

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
Using plain Redux instead of RTKMassive boilerplate, error-proneAlways use Redux Toolkit
Mutating state without ImmerBreaks immutability, bugsUse createSlice with Immer built-in
Storing everything in ReduxUnnecessary complexityKeep component state local when possible
Not using typed hooksLoses type safetyExport typed useAppDispatch/useAppSelector
Fetching data in components with thunksDuplicates request logicUse RTK Query for data fetching
Not using createAsyncThunkManual loading/error handlingUse createAsyncThunk for async actions
Persisting entire stateLarge localStorage, slow hydrationOnly persist auth/critical slices
No error handling in thunksSilent failuresUse rejectWithValue in thunks
Circular dependencies between slicesHard to maintain, bugsUse middleware or separate selectors
Not normalizing nested dataSlow updates, complex reducersNormalize with @reduxjs/toolkit/normalizr

Quick Troubleshooting

IssueCauseSolution
"Cannot read property of undefined"State hydration race conditionAdd loading checks or use skipHydration
Actions not triggering re-rendersNot using typed selectorsUse useAppSelector with proper typing
"Invariant violation" in ReduxState mutation outside ImmerOnly mutate in createSlice reducers
Slow performance with large stateNon-memoized selectorsUse createSelector from Reselect
DevTools not showing actionsDevTools disabled in productionSet devTools: process.env.NODE_ENV !== 'production'
RTK Query cache not invalidatingMissing or wrong tagsAdd providesTags and invalidatesTags
Serialization errors with persistNon-serializable data in stateAdd to serializableCheck.ignoredActions
Thunk errors not caughtNo error boundaryWrap with error boundary or handle in component

Production Readiness

Store Configuration
// store/index.ts - Production-ready store
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const rootReducer = combineReducers({
  auth: authReducer,
  users: usersReducer,
  [api.reducerPath]: api.reducer,
});

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth'], // Only persist auth
  blacklist: [api.reducerPath], // Don't persist API cache
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(api.middleware),
  devTools: process.env.NODE_ENV !== 'production',
});

setupListeners(store.dispatch);

export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
RTK Query Best Practices
// services/api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const baseQuery = fetchBaseQuery({
  baseUrl: '/api',
  prepareHeaders: (headers, { getState }) => {
    const token = (getState() as RootState).auth.token;
    if (token) {
      headers.set('Authorization', `Bearer ${token}`);
    }
    return headers;
  },
});

const baseQueryWithReauth = async (args, api, extraOptions) => {
  let result = await baseQuery(args, api, extraOptions);

  if (result.error?.status === 401) {
    const refreshResult = await baseQuery('/auth/refresh', api, extraOptions);
    if (refreshResult.data) {
      api.dispatch(setToken(refreshResult.data.token));
      result = await baseQuery(args, api, extraOptions);
    } else {
      api.dispatch(logout());
    }
  }

  return result;
};

export const api = createApi({
  reducerPath: 'api',
  baseQuery: baseQueryWithReauth,
  tagTypes: ['User', 'Post'],
  endpoints: (builder) => ({
    getUsers: builder.query<User[], void>({
      query: () => '/users',
      providesTags: (result) =>
        result
          ? [...result.map(({ id }) => ({ type: 'User' as const, id })), 'User']
          : ['User'],
    }),
    createUser: builder.mutation<User, CreateUserDto>({
      query: (body) => ({
        url: '/users',
        method: 'POST',
        body,
      }),
      invalidatesTags: ['User'],
    }),
  }),
});

export const { useGetUsersQuery, useCreateUserMutation } = api;
Testing Redux
// test-utils.tsx
import { configureStore } from '@reduxjs/toolkit';
import { render, RenderOptions } from '@testing-library/react';
import { Provider } from 'react-redux';

interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
  preloadedState?: Partial<RootState>;
  store?: ReturnType<typeof configureStore>;
}

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    store = configureStore({
      reducer: rootReducer,
      preloadedState,
    }),
    ...renderOptions
  }: ExtendedRenderOptions = {}
) {
  function Wrapper({ children }: { children: React.ReactNode }) {
    return <Provider store={store}>{children}</Provider>;
  }
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

// Usage
test('displays user list', async () => {
  renderWithProviders(<UserList />, {
    preloadedState: {
      users: { items: [{ id: '1', name: 'John' }], loading: false },
    },
  });

  expect(screen.getByText('John')).toBeInTheDocument();
});
Error Handling
// Centralized error handling middleware
const errorMiddleware: Middleware = () => (next) => (action) => {
  if (isRejectedWithValue(action)) {
    const error = action.payload;

    if (error.status === 401) {
      // Handle unauthorized
      store.dispatch(logout());
    }

    // Log to monitoring service
    logError({
      action: action.type,
      error: error.data?.message || 'Unknown error',
    });
  }

  return next(action);
};
Monitoring Metrics
MetricTarget
State serialization time< 50ms
Action dispatch time< 16ms
Cache hit ratio> 80%
Test coverage> 85%
Checklist
  • Typed hooks (useAppDispatch, useAppSelector)
  • RTK Query for data fetching
  • Automatic cache invalidation
  • Token refresh handling
  • Redux Persist for auth state
  • DevTools disabled in production
  • Error handling middleware
  • Test utilities with preloaded state
  • Memoized selectors for derived data
  • No sensitive data in Redux DevTools

Reference Documentation

Comments

Markdown supported