State Management

This page covers creating Redux slices for UI and local state management using Redux Toolkit's createSlice and createEntityAdapter.

When to Use Slices vs RTK Query

Choose the right tool for your state:

State Type
Tool
Examples

Remote data (server-owned)

RTK Query

User profiles, NFTs, catalog items, orders

UI state (client-owned)

createSlice

Filters, modals, view preferences, form state

Normalized collections

createEntityAdapter

Sorted/filtered lists, optimistic updates

Creating a Basic Slice

Use createSlice for simple UI state:

// src/features/ui/ui.slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from '@/app/store';

interface UIState {
  sidebarOpen: boolean;
  modalOpen: boolean;
  viewMode: 'grid' | 'list';
  theme: 'light' | 'dark';
}

const initialState: UIState = {
  sidebarOpen: true,
  modalOpen: false,
  viewMode: 'grid',
  theme: 'light',
};

const uiSlice = createSlice({
  name: 'ui',
  initialState,
  reducers: {
    // Boolean toggles
    sidebarToggled(state) {
      state.sidebarOpen = !state.sidebarOpen;
    },
    
    // Set specific value
    modalOpened(state) {
      state.modalOpen = true;
    },
    
    modalClosed(state) {
      state.modalOpen = false;
    },
    
    // Payload actions
    viewModeChanged(state, action: PayloadAction<'grid' | 'list'>) {
      state.viewMode = action.payload;
    },
    
    themeChanged(state, action: PayloadAction<'light' | 'dark'>) {
      state.theme = action.payload;
    },
    
    // Multiple properties
    uiReset() {
      return initialState;
    },
  },
});

// Export actions
export const {
  sidebarToggled,
  modalOpened,
  modalClosed,
  viewModeChanged,
  themeChanged,
  uiReset,
} = uiSlice.actions;

// Export reducer
export default uiSlice.reducer;

// Export selectors
export const selectSidebarOpen = (state: RootState) => state.ui.sidebarOpen;
export const selectModalOpen = (state: RootState) => state.ui.modalOpen;
export const selectViewMode = (state: RootState) => state.ui.viewMode;
export const selectTheme = (state: RootState) => state.ui.theme;

Using Entity Adapters

For normalized collections (lists with IDs), use createEntityAdapter:

Entity Adapter Methods

State Mutations

Generated Selectors

Complex State Example

Combining multiple concerns in one slice:

Memoized Selectors

Use createSelector from Reselect for computed/derived state:

Async Logic with Extra Reducers

Handle RTK Query or async thunk responses in your slice:

Best Practices

1. Keep State Minimal

2. Use Immer-Friendly Mutations

3. Organize Reducers Logically

4. Type Actions Properly

Testing Slices

Next Steps

Last updated