redux-toolkit

Redux Toolkit Core Knowledge

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "redux-toolkit" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-redux-toolkit

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

Scenario Use Instead

Simple global state (user prefs, theme) zustand for less boilerplate

Server state management tanstack-query or RTK Query only

Vue 3 applications pinia

Small projects or prototypes React Context + hooks or Zustand

Component-local state React useState/useReducer

Anti-Patterns

Anti-Pattern Why It's Bad Correct Approach

Using plain Redux instead of RTK Massive boilerplate, error-prone Always use Redux Toolkit

Mutating state without Immer Breaks immutability, bugs Use createSlice with Immer built-in

Storing everything in Redux Unnecessary complexity Keep component state local when possible

Not using typed hooks Loses type safety Export typed useAppDispatch /useAppSelector

Fetching data in components with thunks Duplicates request logic Use RTK Query for data fetching

Not using createAsyncThunk

Manual loading/error handling Use createAsyncThunk for async actions

Persisting entire state Large localStorage, slow hydration Only persist auth/critical slices

No error handling in thunks Silent failures Use rejectWithValue in thunks

Circular dependencies between slices Hard to maintain, bugs Use middleware or separate selectors

Not normalizing nested data Slow updates, complex reducers Normalize with @reduxjs/toolkit/normalizr

Quick Troubleshooting

Issue Cause Solution

"Cannot read property of undefined" State hydration race condition Add loading checks or use skipHydration

Actions not triggering re-renders Not using typed selectors Use useAppSelector with proper typing

"Invariant violation" in Redux State mutation outside Immer Only mutate in createSlice reducers

Slow performance with large state Non-memoized selectors Use createSelector from Reselect

DevTools not showing actions DevTools disabled in production Set devTools: process.env.NODE_ENV !== 'production'

RTK Query cache not invalidating Missing or wrong tags Add providesTags and invalidatesTags

Serialization errors with persist Non-serializable data in state Add to serializableCheck.ignoredActions

Thunk errors not caught No error boundary Wrap 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

Metric Target

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

  • RTK Query

  • Middleware

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review