Using thunks to perform side effects
We have refactored our application with the capability to update state by dispatching actions.
We did however note that there is a network request being made to the basket service within the Product
component. After the request to the basket service has completed an action is dispatched to add the respective product to the basket state. This type of side effect, which has a direct correlation to our state is perfect for encapsulation within a thunk.
Thunks cannot modify state directly, however, they can dispatch actions to do so. Therefore we can manage the network effect within our thunk, and when it has completed call an action to update our state appropriately. Thunks also have first class support for asynchronous code - i.e. async/await
or Promise
.
Defining a thunk on our model
We are going to refactor our basket model slightly, defining a thunk that will make a call to our basketService
and ultimately call an action to update our state when the call to the service has completed.
// src/model/basket-model.js
import { action, thunk } from 'easy-peasy';
// 👆 add the import
import * as basketService from '../services/basket-service';
// 👆 import the mock service
const basketModel = {
productIds: [2],
// add a new action which we can call when the call to the basket
// 👇 service has completed
addedProduct: action((state, payload) => {
state.productIds.push(payload);
}),
// 👇 refactor our addProduct action into a thunk which will call the service
addProduct: thunk(async (actions, payload) => {
// call our service
await basketService.addProductToBasket(payload);
// then dispatch an action to update state
actions.addedProduct(payload);
}),
// ...
Quite a hefty update, but a lot of behaviour has now been encapsulated within our store.
Thunks can be asynchronous or synchronous. If you use
async/await
or return aPromise
from your thunk it will be considered asynchronous. Easy Peasy has special logic to monitor asynchronous thunks and will ensure listeners (we will cover them later) are only dispatched when an asynchronous thunk has resolved. A use-case for a synchronous thunk would be to encapsulate if/else logic around the dispatching of actions. That being said, using thunks in an asynchronous manner to encapsulate side effects is by far the more common use-case.
It is important to remember that thunks are unable to update state directly - they are instead provided the local actions and thunks via the actions
argument. We can dispatch the provided actions to update our state appropriately.
You can dispatch as many actions as you like. Consider the case of dispatching different actions to represent a failed or successful network request.
Refactoring our Product component
We will now refactor our Product
component, removing the references to the basketService.
import React, { useCallback, useState } from 'react';
import { useStoreActions, useStoreState } from 'easy-peasy';
- import * as basketService from '../services/basket-service';
export default function Product({ id }) {
const addProductToBasket = useStoreActions(
actions => actions.basket.addProduct,
);
// ...
const onAddToBasketClick = useCallback(async () => {
setAdding(true);
- await basketService.addProductToBasket(product.id);
- addProductToBasket(product.id);
+ await addProductToBasket(product.id);
setAdding(false);
}, [product]);
// ...
As we refactored our addProduct
action into a thunk we don't change our useStoreActions
code, instead we are prefixing our addProduct
dispatch with an an await
, leveraging the Promise
that will be returned by our asynchronous thunk. This allows us to maintain the existing behaviour around setting the adding
flag which indicates to the UI when the adding operation is in progress.
Once you have made this change you will be able to run your application and then test the thunk by adding a product to your basket.
Review
We have successfully incorporated side effects within our store via a thunk. In the next section we will introduce the computed API, which allows us to represent derived data whilst also introducing nice performance characteristics.
You can view the progress of our application refactor here.