Exploring Redux Data Flow & Thunk
In this article we will start with a review of Redux data flow and then go in to where Thunk fits in to the grand scheme of things. We will start with the central theme of Redux which is that state is stored within a central location. This location is an object called store which is created once at the top level of the app with a method imported from Redux called createStore. this createStore method needs to be passed to your reducer function along with any middleware you might need…but more on that later. All components can access this store object once it is passed into the <Provider>(which should wrap the rest of your component tree starting with the second highest component). In Javascript all objects are mutable meaning that changes made occur to the original object. An immutable change is a copy of the original object that you then make a change to thus the need for store. All state changes in Redux are done in this immutable way in order to preserve the various other state values that need not be changed. Redux selects the part of this store object it would like to change with something called an Action object.
What is an Action?
an action is an object (like previously noted) that will eventually be used to describe and deliver a change to the store. This object typically has two keys but can have more. By most conventions these keys are called type and payload. The ‘type’ key contains a value (in the following example an uppercase string) that is used to match a condition in the reducer in order to find the appropriate way to update the store. The ‘payload’ key contains the new data we wish to update our store with. The logic for producing this object is created by a type of function called an Action Creator.
const addToCount = (count) => {
let newCount = count + 1
return dispatch => {
dispatch({type: "SET_COUNT", payload: newCount})
}
}
In the above snippet the function ‘addToCount’ is the action creator and the action object is {type: “SET_COUNT”, payload: newCount}. Actions are then sent to a specific type of function known as a Reducer.
What is a Reducer?
A reducer is a special function that receives the current state along with an action object as arguments. The reducer is a pure function in that it never changes the value of its arguments or creates side effects. It instead creates a copy of the state that was passed to it which has the updates from the payload key of the action object. It typically uses a switch statement to match the ‘type’ field of the action object in order to know where the update contained within the actions payload key needs to be applied. However this can be done by any logic that checks the condition of the type field and matches it with the appropriate change. To continue with our counting example a reducer function might look like this:
const initialState = { name: 'seeker', count: 0 }function counterReducer(state = initialState, action) {
switch(action.type){
case 'SET_COUNT':
return {...state, count: action.payload}
case 'SET_NAME':
return {...state, name: action.payload}
default:
return {...state}
}
}
As you saw in the first code snippet this action object is being passed to this reducer function by something called dispatch.
What is Dispatch?
dispatch is a method contained in the Redux store that as mentioned passes an action object to the stores reducer function. The store will then be updated with its new value. Sometimes when dispatching an action object we need to make sure that all of the logic and side effects are resolved within the action creator first before it tries to assemble the action object. This is done via our next topic Thunk.
What is Thunk?
Thunk is a middleware which you import at the top layer of your app and pass as a second argument to your createStore method. Because I have been mentioning this top layer of your app and how to set it up a lot at this point I feel it necessary to provide a lengthy example with some of what we have gone over highlighted:
The thunk provides a secondary function that executes before dispatching an action object thus making sure that all side effects and logic are complete before assembling the action. This is particularly useful when it comes to functions that you wish to operate in an asynchronous fashion like a fetch which will return a promise until the fetch manages to resolve. Typically thunk will call dispatch (which again is provided by store) on the return value of said function. You can see this in the bold segment of the following:
/actionCreator.jsexport const getLocation = (id) => {
return dispatch => fetch(`${url}/locations/${id}`)
.then(response => response.json())
.then(result => dispatch({type: "GET_LOCATION",payload: result}))
}
the action creator ‘getLocation’ is imported into some component within the User Interface by either using connect as well as mapDispatchToProps or a useDispatch hook. the secondary function within ‘getLocation’ receives dispatch as an argument and will resolve before dispatching the action object thanks to thunk. The resulting dataflow resembles something like this:
In closing thunk allows for a much more predictable interaction with our Redux store by making sure our action objects are always setup the way we intend them to be for a specific interaction with a specific instance of the store. This has been a basic overview of Redux dataflow and how and where thunk middleware fits in.