The data management system that we use on the front end for every new feature is based on the architecture that I documented, prototyped, and taught to the rest of the front end team.
Creating a New Feature with Redux & TypeScript
POC source code:
1. Define Types for Slice Initial State
When creating a new feature, start by defining types for the slice's initial state.
- The initial state should always be an object with keys, allowing for easy expansion.
- Redux Toolkit comes with Immer, which lets you mutate state immutably.
- You cannot replace the initial state object entirely; ensure it has a base object structure instead of just an array.
Example:
// Define the shape of your state
type TasksState = {
entities: Task[]
}
const initialState: TasksState = {
entities: []
}
2. Creating the Redux Slice
Use createSlice
to set up your reducer, create actions, and wire up TypeScript types.
const tasksSlice = createSlice({
name: 'tasks',
initialState,
reducers: {
addTask: (state, action: PayloadAction<Task>) => state,
removeTask: state => state
},
extraReducers: builder => {
// Define external cases if needed
}
})
Accessing actions:
tasksSlice.actions
3. Flux Standard Actions
Redux actions follow a convention where they include:
type
: Describes the actionpayload
: Contains the data associated with the action
Example:
{ type: "hello", payload: {} }
4. Understanding Slices
Slices consist of:
createAction
: A standard way to create an action creator.- Action creators format the action before dispatching.
Example:
const incrementAction = createAction('INCREMENT')
incrementAction.type // 'INCREMENT'
5. Builder Callback Functions
- Define cases for each action inside the reducer.
- Mutations can be performed on the state object, as Immer creates a "WriteableDraft" of the state.
6. Utility Types
Utility types should be globally defined.
Required<T>
Ensures that a type has required properties.
Pick<T, P extends keyof T>
Combines Partial<T>
and Task
by selecting specific properties.
type Only<T, P extends keyof T> = Pick<T, P>
This approach ensures type safety while maintaining flexibility in Redux state management.