redux-best-practices

Redux and React-Redux best practices, patterns, and anti-patterns based on the official Redux style guide. Use when writing, reviewing, or refactoring Redux code including store setup, slices, reducers, selectors, async logic, state normalization, or React-Redux hooks. Triggers on tasks involving createSlice, configureStore, useSelector, useDispatch, thunks, RTK Query, or state management architecture decisions.

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-best-practices" with this command: npx skills add felipeorlando/redux-best-practices/felipeorlando-redux-best-practices-redux-best-practices

Redux Best Practices

Essential Rules (Priority A)

These rules prevent errors. Violating them causes bugs.

1. Never Mutate State

// ❌ WRONG - mutates state
state.todos.push(newTodo)
state.user.name = 'New Name'

// ✅ CORRECT - RTK with Immer handles this
const todosSlice = createSlice({
  reducers: {
    todoAdded: (state, action) => {
      state.push(action.payload) // Immer makes this safe
    }
  }
})

2. Reducers Must Be Pure

Forbidden in reducers:

  • Async logic (AJAX, timeouts, promises)
  • Math.random(), Date.now()
  • External variable modifications

3. Keep State Serializable

Never store: Promises, Symbols, Maps/Sets, Functions, Class instances, DOM nodes

4. One Store Per App

// store.ts - single source of truth
export const store = configureStore({
  reducer: { todos: todosReducer, users: usersReducer }
})

Strongly Recommended (Priority B)

Use Redux Toolkit

Always use RTK. It enables DevTools, catches mutations, uses Immer, reduces boilerplate.

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

const todosSlice = createSlice({
  name: 'todos',
  initialState: [] as Todo[],
  reducers: {
    todoAdded: (state, action: PayloadAction<Todo>) => {
      state.push(action.payload)
    }
  }
})

export const { todoAdded } = todosSlice.actions

Feature-Based Structure

src/
├── app/
│   ├── store.ts
│   └── hooks.ts (typed useSelector/useDispatch)
├── features/
│   ├── todos/
│   │   ├── todosSlice.ts
│   │   ├── todosSelectors.ts
│   │   └── TodoList.tsx
│   └── users/
│       ├── usersSlice.ts
│       └── ...

Normalize Complex State

// ❌ Nested - hard to update
{ posts: [{ id: 1, author: { id: 1, name: 'Alice' } }] }

// ✅ Normalized - easy to update
{
  posts: { byId: { '1': { id: '1', authorId: '1' } }, ids: ['1'] },
  users: { byId: { '1': { id: '1', name: 'Alice' } }, ids: ['1'] }
}

Model Actions as Events

// ❌ Setter actions
dispatch({ type: 'SET_PIZZA_COUNT', payload: 1 })
dispatch({ type: 'SET_COKE_COUNT', payload: 1 })

// ✅ Event actions
dispatch({ type: 'food/orderPlaced', payload: { pizza: 1, coke: 1 } })

Treat Reducers as State Machines

interface FetchState {
  status: 'idle' | 'loading' | 'succeeded' | 'failed'
  data: Data | null
  error: string | null
}

// Only allow valid transitions
builder
  .addCase(fetchData.pending, (state) => {
    if (state.status === 'idle') state.status = 'loading'
  })

Use React-Redux Hooks

// ❌ Old connect HOC
export default connect(mapState, mapDispatch)(Component)

// ✅ Hooks
const todos = useSelector(selectTodos)
const dispatch = useDispatch()

Multiple Granular useSelector Calls

// ❌ Selecting too much - rerenders on any user change
const user = useSelector(state => state.user)

// ✅ Granular - only rerenders when name changes
const name = useSelector(state => state.user.name)
const email = useSelector(state => state.user.email)

Connect More Components

// ✅ Parent selects IDs, child selects individual item
const UserList = () => {
  const ids = useSelector(selectUserIds)
  return ids.map(id => <UserItem key={id} userId={id} />)
}

const UserItem = ({ userId }) => {
  const user = useSelector(state => selectUserById(state, userId))
  return <div>{user.name}</div>
}

Recommended Patterns (Priority C)

Selector Naming: selectThing

export const selectTodos = (state: RootState) => state.todos
export const selectTodoById = (state: RootState, id: string) =>
  state.todos.entities[id]
export const selectCompletedTodos = createSelector(
  [selectTodos],
  todos => todos.filter(t => t.completed)
)

Action Type Format: domain/eventName

// ✅ RTK default
'todos/todoAdded'
'users/userLoggedIn'

// ❌ Old SCREAMING_SNAKE_CASE
'ADD_TODO'

Async Logic with Thunks

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async (_, { rejectWithValue }) => {
    try {
      return await todosAPI.fetchAll()
    } catch (err) {
      return rejectWithValue(err.message)
    }
  }
)

// Handle in slice
extraReducers: builder => {
  builder
    .addCase(fetchTodos.pending, state => { state.status = 'loading' })
    .addCase(fetchTodos.fulfilled, (state, action) => {
      state.status = 'succeeded'
      state.items = action.payload
    })
    .addCase(fetchTodos.rejected, (state, action) => {
      state.status = 'failed'
      state.error = action.payload as string
    })
}

Use RTK Query for Data Fetching

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (builder) => ({
    getTodos: builder.query<Todo[], void>({
      query: () => 'todos'
    }),
    addTodo: builder.mutation<Todo, Partial<Todo>>({
      query: (body) => ({ url: 'todos', method: 'POST', body })
    })
  })
})

export const { useGetTodosQuery, useAddTodoMutation } = api

Anti-Patterns to Avoid

Anti-PatternWhy BadSolution
Form state in ReduxPerformance overhead, not globalLocal useState
UI state in ReduxModal open/closed isn't globalComponent state
Blind spread return action.payloadLoses reducer ownershipExplicit field mapping
Sequential dispatchesMultiple renders, invalid statesSingle event action
Deeply nested stateComplex updatesNormalize
Side effects in reducersBreaks time-travel debugUse thunks/middleware
Selecting entire state sliceUnnecessary rerendersGranular selectors
Immutable.jsBundle bloat, API infectionUse Immer (built into RTK)

When NOT to Use Redux

Keep in local component state:

  • Form input values (dispatch on submit only)
  • UI toggles (modal open, dropdown expanded)
  • Animation state
  • Hover/focus states
  • Data only used by one component

Use Redux for:

  • User authentication
  • Shopping cart
  • Cached API data
  • App-wide notifications
  • Cross-component shared state

Type-Safe Setup

// app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

References

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

github-tools

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.

Archived SourceRecently Updated
Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated
Coding

ask-claude

Delegate a task to Claude Code CLI and immediately report the result back in chat. Supports persistent sessions with full context memory. Safe execution: no data exfiltration, no external calls, file operations confined to workspace. Use when the user asks to run Claude, delegate a coding task, continue a previous Claude session, or any task benefiting from Claude Code's tools (file editing, code analysis, bash, etc.).

Archived SourceRecently Updated
Coding

ai-dating

This skill enables dating and matchmaking workflows. Use it when a user asks to make friends, find a partner, run matchmaking, or provide dating preferences/profile updates. The skill should execute `dating-cli` commands to complete profile setup, task creation/update, match checking, contact reveal, and review.

Archived SourceRecently Updated