Thunks in Redux, What and Why
What is a Redux thunk and why do I need it?
To interact with a given Redux-store we usually invoke an event with a certain action type. Sometimes we might even put some additional data as payload inside of the event.
But what if you want to do some data manipulation or, beware, some async data fetching? This is where thunks in redux come in handy. They are basically ‘super events’ for redux that not only can interact (getState
and dispatch
action) with the store, but also handle async and sync processes.
How do I do this?
If your are using the Redux toolkit, it’s already installed in your project.
Otherwise we need the middleware (Github).
npm install redux-thunk
And we need to register the middleware like so:
//store.js
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import rootReducer from "./reducers/index"
const store = createStore(rootReducer, applyMiddleware(thunk))
export default store
Creating the thunk
Let’s say we want to call an API that requires userID
and returns user data that we want to store in our redux store.
To do this we need a nested function. The outer function provides the parameter and returns the async function that provides the dispatch
and getState
parameters to interact with the redux store.
The most basic version would look like this:
//api-call.thunk.js
import { getUserData } from "../get-user-data.js"
const apiCallThunk = userID => async (dispatch, getState) => {
//Some async api call
const userData = await getUserData(userID)
//Dispatch redux event
dispatch({
type: "set-user-data",
payload: {
userData: userData,
},
})
}
export default apiCallThunk
The store would look like this:
//store.js
const startingState = null
const reducer = (state = startingState, action) => {
switch (action.type) {
case "set-user-data":
return action.payload.userData
default:
return state
}
}
export const store = createStore(reducer)
Adding checks to the thunk
Within the thunk, we not only can call an async api, we can also check for errors or existing data like so:
//api-call-v2.thunk.js
import { getUserData } from "../get-user-data.js"
import { someTypeOfErrorChecking } from "../error-checking.js"
const apiCallThunk = userID => async (dispatch, getState) => {
//If we already have a user in store, don't call the api and end the thunk
if (getState() !== null) return
//Some async api call
const userData = await getUserData(userID)
//If the api call doesn't succeed, don't dispatch the event
if (someTypeOfErrorChecking(userData)) return
//Dispatch redux event
dispatch({
type: "set-user-data",
payload: {
userData: userData,
},
})
}
export default apiCallThunk