Implementing Advanced State Management in TypeScript Applications
Explore techniques for managing complex state in TypeScript applications using modern state management libraries.
0 likes
13 views
Rule Content
--- description: Enforce best practices for advanced state management in TypeScript applications using modern state management libraries. globs: ['**/*.ts', '**/*.tsx'] tags: [TypeScript, State Management, Best Practices] priority: 3 version: 1.0.0 --- # Implementing Advanced State Management in TypeScript Applications ## Context - Applicable to TypeScript projects utilizing state management libraries such as Redux, Zustand, or MobX. - Aims to ensure type safety, maintainability, and scalability in state management implementations. ## Requirements 1. **Use TypeScript's Strict Mode** - Enable strict mode in `tsconfig.json` to enforce type safety. - Example `tsconfig.json` configuration: ```json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true } } ``` 2. **Define Explicit Interfaces for State** - Create interfaces to define the shape of the state to ensure consistency and type safety. - Example: ```typescript interface Todo { id: string; text: string; completed: boolean; } interface TodoState { todos: Todo[]; filter: 'all' | 'active' | 'completed'; } ``` 3. **Use Enums for State Constants** - Utilize enums to define state-related constants for better readability and maintainability. - Example: ```typescript enum Filter { All = 'all', Active = 'active', Completed = 'completed' } ``` 4. **Implement Immutability in State Updates** - Ensure state updates are performed immutably to prevent unintended side effects. - Example using Redux Toolkit: ```typescript import { createSlice, PayloadAction } from '@reduxjs/toolkit'; const todosSlice = createSlice({ name: 'todos', initialState: [] as Todo[], reducers: { addTodo: (state, action: PayloadAction<Todo>) => { state.push(action.payload); }, toggleTodo: (state, action: PayloadAction<string>) => { const todo = state.find(todo => todo.id === action.payload); if (todo) { todo.completed = !todo.completed; } } } }); ``` 5. **Use Middleware for Side Effects** - Handle asynchronous operations and side effects using middleware like Redux Thunk or Redux Saga. - Example using Redux Thunk: ```typescript import { createAsyncThunk } from '@reduxjs/toolkit'; export const fetchTodos = createAsyncThunk( 'todos/fetchTodos', async () => { const response = await fetch('/api/todos'); return (await response.json()) as Todo[]; } ); ``` 6. **Organize State Management Code by Feature** - Structure state management code into feature-based modules to enhance maintainability. - Example directory structure: ``` src/ ├── features/ │ ├── todos/ │ │ ├── components/ │ │ ├── services/ │ │ ├── store/ │ │ └── TodoContainer.tsx ├── app/ │ ├── store.ts │ └── rootReducer.ts ├── components/ ├── services/ ├── App.tsx └── index.tsx ``` 7. **Use Type Guards for Type Safety** - Implement type guards to ensure type safety during runtime. - Example: ```typescript function isTodo(obj: any): obj is Todo { return obj && typeof obj.id === 'string' && typeof obj.text === 'string' && typeof obj.completed === 'boolean'; } ``` 8. **Leverage Utility Types for State Management** - Utilize TypeScript utility types like `Partial`, `Readonly`, and `Pick` to manage state effectively. - Example: ```typescript type ReadonlyTodo = Readonly<Todo>; type PartialTodo = Partial<Todo>; ``` 9. **Avoid Using `any` Type** - Refrain from using the `any` type to maintain type safety and code quality. - Instead, use `unknown` and perform proper type checks. 10. **Document State Management Logic** - Provide clear documentation for state management logic to facilitate understanding and maintenance. - Example: ```typescript /** * Adds a new todo to the state. * @param state - The current state of todos. * @param action - The action containing the new todo. */ addTodo: (state, action: PayloadAction<Todo>) => { state.push(action.payload); } ``` ## Examples <example> **Good Example: Using TypeScript Interfaces and Enums in Redux Toolkit** import { createSlice, PayloadAction } from '@reduxjs/toolkit'; enum Filter { All = 'all', Active = 'active', Completed = 'completed' } interface Todo { id: string; text: string; completed: boolean; } interface TodoState { todos: Todo[]; filter: Filter; } const initialState: TodoState = { todos: [], filter: Filter.All }; const todosSlice = createSlice({ name: 'todos', initialState, reducers: { addTodo: (state, action: PayloadAction<Todo>) => { state.todos.push(action.payload); }, toggleTodo: (state, action: PayloadAction<string>) => { const todo = state.todos.find(todo => todo.id === action.payload); if (todo) { todo.completed = !todo.completed; } }, setFilter: (state, action: PayloadAction<Filter>) => { state.filter = action.payload; } } }); </example> <example type="invalid"> **Bad Example: Using `any` Type and Mutating State Directly** import { createSlice } from '@reduxjs/toolkit'; const todosSlice = createSlice({ name: 'todos', initialState: [] as any[], reducers: { addTodo: (state, action) => { state.push(action.payload); // Direct mutation without type safety }, toggleTodo: (state, action) => { const todo = state.find(todo => todo.id === action.payload); if (todo) { todo.completed = !todo.completed; // Direct mutation without type safety } } } }); </example>