evolve-and-diagnose-redux-apps/migrate-to-modern-redux
Use this when moving a legacy Redux codebase to current RTK patterns. Covers replacing createStore with configureStore, migrating touched reducers to createSlice, codemod-assisted RTK 2 updates, and replacing server-data stacks with RTK Query instead of writing new legacy Redux code.
- 0
- Installs
- —
- Rating
- —
- Success rate
- 1
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 e54f131158a2c6ec… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
Migrate To Modern Redux
Setup
// before
import { applyMiddleware, combineReducers, createStore } from 'redux'
import thunk from 'redux-thunk'
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const usersReducer = (state = [] as { id: string; name: string }[]) => state
const rootReducer = combineReducers({
posts: postsReducer,
users: usersReducer,
})
export const legacyStore = createStore(rootReducer, applyMiddleware(thunk))
// after
import { configureStore } from '@reduxjs/toolkit'
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const usersReducer = (state = [] as { id: string; name: string }[]) => state
export const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer,
},
})
Core Patterns
Replace the store setup first
import { configureStore } from '@reduxjs/toolkit'
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const usersReducer = (state = [] as { id: string; name: string }[]) => state
export const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
This is the one migration step that can happen immediately while old reducers continue to work.
Migrate reducers as you touch them
import { createSlice } from '@reduxjs/toolkit'
type TodosState = {
items: { id: string; text: string; completed: boolean }[]
}
const initialState: TodosState = {
items: [],
}
export const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
todoAdded(state, action: { payload: { id: string; text: string } }) {
state.items.push({ ...action.payload, completed: false })
},
todoToggled(state, action: { payload: { id: string } }) {
const todo = state.items.find((item) => item.id === action.payload.id)
if (todo) {
todo.completed = !todo.completed
}
},
},
})
Once a reducer needs editing, migrate that reducer instead of adding more legacy code to it.
Use codemods for mechanical updates
npx @reduxjs/rtk-codemods createSliceBuilder src/features/posts/postsSlice.ts
npx @reduxjs/rtk-codemods createReducerBuilder src/features/posts/postsReducer.ts
Use codemods for repetitive RTK API migrations, then review the result and finish the semantic cleanup by hand.
Replace legacy server-data stacks with RTK Query
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Todo = { id: string; text: string }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (build) => ({
getTodos: build.query<Todo[], void>({
query: () => 'todos',
}),
}),
})
When the old code is just request status plus fetched data, migrate toward RTK Query instead of carrying the thunk stack forward forever.
Common Mistakes
HIGH Attempting a big-bang rewrite
Wrong:
// Replace every reducer, every connected component, and every async flow
// in one branch before shipping anything.
Correct:
// 1) Switch createStore to configureStore
// 2) Migrate one touched reducer to createSlice
// 3) Convert touched connected components to hooks
// 4) Repeat without introducing new legacy Redux code
Modern Redux migration is incremental, but once the store is modernized new work should stop adding legacy patterns.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
CRITICAL Carrying removed RTK 2 config forms forward
Wrong:
configureStore({
reducer,
middleware: [logger],
})
Correct:
configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
})
RTK 2 removed array middleware configuration and other older builder forms that agents trained on RTK 1.x still emit.
Source: reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md
HIGH Preserving hand-written fetch state by default
Wrong:
import { createAsyncThunk } from '@reduxjs/toolkit'
type Todo = { id: string; text: string }
const initialState = {
items: [] as Todo[],
status: 'idle' as 'idle' | 'pending' | 'failed',
}
export const fetchTodos = createAsyncThunk('todos/fetch', async () => {
const response = await fetch('/api/todos')
return (await response.json()) as Todo[]
})
Correct:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Todo = { id: string; text: string }
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (build) => ({
getTodos: build.query<Todo[], void>({
query: () => 'todos',
}),
}),
})
If the feature is really server cache, keep the migration moving toward RTK Query instead of rebuilding the old loading-flag architecture in new APIs.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
Files
1- SKILL.md
f97a145a095.7 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from reduxjs/redux-toolkit8
Use this when setting up a new Redux Toolkit app or modernizing an existing React + Redux codebase. Covers configureStore, Provider wiring, typed hooks, hooks-first React-Redux usage, feature folders, and the correct store lifetime for SPA and SSR-heavy React environments.
Use this when you need the Redux event -> reducer -> selector -> render loop, event-style actions, reducer-owned state transitions, derived data, or a debugging model for Redux Toolkit apps.
Use this when debugging duplicate requests, stale cache behavior, broad subscriptions, selector churn, serializability warnings, or other Redux Toolkit and RTK Query bugs. Covers a practical event -> reducer -> selector -> render debugging loop plus RTK Query cache interpretation.
Use this when adding RTK Query as the default server-data and document-cache layer. Covers createApi, store integration, hooks, invalidation behavior, optimistic updates, and deciding when RTK Query is the right cache model.
Use this when generating RTK Query endpoints from OpenAPI schemas with @rtk-query/codegen-openapi. Covers the empty API pattern, filterEndpoints, endpointOverrides, generated tags, and reviewing generated output before it becomes part of the app.
Use this when authoring or refactoring slices with createSlice, selectors, create.asyncThunk, entity adapters, or lazy reducer injection. Covers Immer-backed mutation syntax, slice selectors, getSelectors, injectInto, withLazyLoadedSlices, and current RTK 2 slice patterns.
Use this when deciding whether data belongs in Redux, component state, router state, or another external source. Covers state ownership, authority boundaries, slice sizing, and when to move or split data as the app evolves.
Use this when choosing between RTK Query, createAsyncThunk, handwritten thunks, and createListenerMiddleware. Covers imperative versus reactive workflows, listener middleware setup, and keeping side effects out of reducers and UI components.
Related backend skillsscan passed
Build a new API connector or provider by matching the target repo's existing integration pattern exactly. Use when adding one more integration without inventing a second architecture.
Report browser/API/CLI/job/worker/webhook bugs. (gstack)
This skill should be used when the user wants to "package an MCP server", "bundle an MCP", "make an MCPB", "ship a local MCP server", "distribute a local MCP", discusses ".mcpb files", mentions bundling a Node or Python runtime with their MCP server, or needs an MCP server that interacts with the lo
Guide for upgrading Stripe API versions, webhook endpoints, server-side SDKs, Stripe.js, and mobile SDKs
PostHog integration for server-side Node.js applications using posthog-node
Create and compose tRPC middleware with t.procedure.use(), extend context via opts.next({ ctx }), build reusable middleware with .concat() and .unstable_pipe(), define base procedures like publicProcedure and authedProcedure. Access raw input with getRawInput(). Logging, timing, OTEL tracing pattern