-
Notifications
You must be signed in to change notification settings - Fork 14
Writing state slices with Redux Toolkit
A slice is a section of Redux state pertaining to a particular concern- for example personal details, or product selection. A slice has its own reducers and action creators, and its actions are scoped to the slice.
Redux Toolkit makes it extremely easy to define and scope a state slice, using the createSlice
utility.
const slice = createSlice({
name: 'count',
initialState: {
count: 0
},
reducers: {
increment(state, action: PayloadAction<number>) {
state.count += action.payload
},
},
})
// now available:
slice.reducer // reducer to use in store creation
slice.actions.increment(2) // action creator
The slice.actions.increment
action creator creates an action with the following shape:
{
type: 'count/increment',
payload: 2
}
Redux Toolkit uses Immer internally, which removes the burden of managing state immutability ourselves. The state parameter passed to each reducer function is actually a 'writable draft' copy of the state, allowing us to make mutative changes to it which are then added to the next iteration of the real, immutable state. The state type is inferred from the type of the initial state.
Actions created by slice action creators are always of the PayloadAction
type, meaning that any information carried with the action (such as the amount to increment by, or the new value of a form field) is under the payload
key on the object. This both simplifies the code and removes the need to define our own action types. The type
of the action will be automatically scoped to the name of the slice, meaning that we can have, for example, a setEmail
action for payee details and a setEmail
action for giftee details that will never conflict.
While a slice 'owns' its section of state, reducers and actions, it may also be desirable to respond to actions that belong to other slices- for example, a change to the delivery country that affects available payment options. This can be achieved by adding an extraReducers
field in slice creation:
import { commonSlice } from '../common';
const { resetAll } = commonSlice.actions;
const slice = createSlice({
name: 'count',
initialState: {
count: 0
},
reducers: {
increment(state, action: PayloadAction<number>) {
state.count += action.payload
},
},
extraReducers: (builder) => {
builder
.addCase(resetAll, (state, action) => {
state.count = 0;
})
},
})
The builder callback approach used here ensures type safety with no need to write out the types ourselves- by using a reference to the action creator from the other slice, the type of action
in the callback passed to addCase
will be inferred from the action creator.
You should very rarely, if ever, be writing Redux code outside of a slice; however if necessary Toolkit also provides a createReducer helper as well as a mechanism for defining custom async thunks.
- Redux Glossary
- Why Redux Toolkit?
- Writing state slices with Redux Toolkit
- Handling action side effects in Redux
- Presentational and Container Components
- Scoped actions and reducers
- Server Side Rendering
- Form validation
- CI build process
- Post deployment testing
- Post deployment test runbook
- TIP Real User Testing
- Code testing and validation
- Visual testing
- Testing Apple Pay locally
- Test Users
- Deploying to CODE
- Automated IT tests
- Deploying Fastly VCL Snippets
- Archived Components
- Authentication
- Switchboard
- How to make a fake contribution
- The epic and banner
- Environments
- Tech stack
- Supported browsers
- Contributions Internationalisation
- Payment method internationalisation in Guardian Weekly
- Print fulfilment/delivery
- Updating the acquisitions model
- Runscope testing
- Scala Steward for dependency management
- Alarm Investigations
- Ticker data
- Ophan
- Quantum Metric
- [Google Tag Manager] (https://github.com/guardian/support-frontend/wiki/Google-Tag-Manager)