Redux Toolkit Quick Reference
Note: This guide does not focus on concrete implementations of specific features or functionalities. Instead, it serves as a quick reference to common tasks and operations you may encounter when working with Redux Toolkit in a React Native application.
- Quick setup steps
- Things to remember
-
Create an async thunk using
createAsyncThunk - Payload creator techniques
-
Add Redux to a component with
useSelectoranduseDispatchhooks - Updating and deleting state items
- Things to check when your having a bad day
Quick setup steps
- Define the initial state by creating a constant object with the initial values for the slice's state.
-
Create the slice using
createSlice, providing the initial state and reducers. - Export the reducer function from the slice for use in the Redux store.
-
Export any actions that were generated by
createSlicefor use in your components. -
Add the slice's reducer to the store in the
reducerfield of theconfigureStore. - Use
useSelectorto access the slice's state in your components. - Use
useDispatchto dispatch actions to the slice from your components.
Things to remember
- An action within a reducer contains two properties:
typeandpayload.- The
payloadproperty is an object that holds the data transferred to the action. - the
typeproperty is a string that identifies the action type.
- The
Create an async thunk using createAsyncThunk
- Import
createAsyncThunkfrom@reduxjs/toolkit. - Define an async thunk using
createAsyncThunkto handle asynchronous logic including the action type string and the "payload creator" function(s). - Update the
extraReducersin the slice to include the async thunk includingpending,fulfilled, andrejectedactions.
// src/features/feature/featureSlice.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
const initialState = {
data: {},
isLoading: false,
error: null
};
export const someActionAsync = createAsyncThunk(
'feature/someAction',
async (value, thunkAPI) => {
try {
// Code to perform an asynchronous operation and return the data
} catch (error) {
return thunkAPI.rejectWithValue(error.message);
}
}
);
const featureSlice = createSlice({
name: 'feature',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(someActionAsync.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(someActionAsync.fulfilled, (state, action) => {
state.loading = false;
state.error = null;
state.data = action.payload;
})
.addCase(someActionAsync.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message;
});
}
});
export default featureSlice.reducer;
Payload creator techniques
Verifying the payload creator parameter has been passed
export const fetchFeatureById = createAsyncThunk(
'feature/fetchFeatureById',
async (id, thunkAPI) => {
if(!id) thunkAPI.rejectWithValue('Feature ID can not be empty');
// Code to perform an asynchronous operation and return the data
}
);
Handle the error in the rejected case of the extraReducers:
extraReducers: (builder) => {
builder
.addCase(fetchFeatureById.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
});
}
Check if the data is already in the state (by ID)
This is dependent on the structure of your state object. If the data is stored in an object where the key is the ID of the data, you can check if the data already exists in the state before making an API call.
const initialState = {
data: {
'1': { id: '1', name: 'Feature 1', description: 'This is feature 1', },
'2': { id: '2', name: 'Feature 2', description: 'This is feature 2', },
},
};
export const fetchFeatureById = createAsyncThunk(
'feature/fetchFeatureById',
async (id, thunkAPI) => {
const existing = thunkAPI.getState().feature.data[id];
// If the feature already exists in the state, return it
if(existing) return state.feature.data[id];
// else, perform an asynchronous operation and return the data
}
);
Check for existing state when store data is an object
export const fetchProductsByCategory = createAsyncThunk(
'feature/fetchProductsByCategory',
async (feature, thunkAPI) => {
// If no feature is provided, reject the promise with a value of 'feature not found'
if (!feature) return thunkAPI.rejectWithValue('feature not found');
// Get the products for the feature from the state
const existing = thunkAPI.getState().products.data[feature];
// If the products for this feature already exist in the state, return them
if (existing) return { [feature]: existing };
// Code to perform an asynchronous operation and return the data
}
);
Add Redux to a component with useSelector and useDispatch hooks
// src/components/MyComponent.jsx
import { useSelector, useDispatch } from 'react-redux';
import { action1, action2, selectFeatureState } from '../features/feature/featureSlice';
function MyComponent() {
const { data, isLoading, error } = useSelector(state => state.feature);
const dispatch = useDispatch();
useEffect(() => {
dispatch(someAction());
}, []);
}
Updating and deleting state items
Update an existing item in an array of objects (TBD)
Delete an item from an array of objects (Immer)
reducers: {
removeItemById: (state, action) => {
const {id} = action.payload;
const itemIndex = state.findIndex(item => item.id === id);
if (itemIndex !== -1) {
state.splice(itemIndex, 1);
}
},
},
Whats happening here?
- The
removeItemByIdreducer function takes two parameters:stateandaction. - The
actionobject contains apayloadwith theidof the item to be removed. - The
findIndexmethod is used to find the index of the item in thestatearray based on itsid. - If
itemIndexis found (i.e., not-1), thesplicemethod is used to remove the item at that index from the state array. - The
splicemethod modifies the state array directly by removing the specified item. - If
itemIndexis-1(i.e., the item is not found), no action is taken.
splice isn't this mutating?
While it's true that splice() is a mutating method, Redux Toolkit uses the Immer
library under the hood, which allows you to write "mutating" logic in a safe way. Immer
works by creating a draft state and applying all mutations to the draft. The original
state is left unmodified, and a new state is returned that includes the applied
mutations.
Things to check when your having a bad day
Understand your state objects
Selecting the entire Redux state object (all slices):
const state = useSelector(state => state)
Selecting a specific feature slice of your state:
const featureState = useSelector(state => state.feature)
Destructuring properties data, error, and isLoading from the feature slice.
const { data, error, isLoading } = useSelector(state => state.feature);
Are you getting the correct slice?
When using the useSelector hook, make sure you are selecting the correct slice of the
state object. This is the second part of the selector function and should match the name
of the slice you defined in the configureStore when setting up the Redux store. For
example, if you have a slice named products, the selector should look like this:
// store.js
export const store = configureStore({
reducer: {
products: productsReducer,
},
});
const { data, error, isLoading } = useSelector(state => state.products);