React-Redux
If you want to avoid Prop Drilling which is to pass props from React parent component to its children or grand children component, you do want to read this tutorial about React-Redux.
What is Redux?
According to Redux documentation, Redux is a pattern and library for managing and updating application state, using events called “actions”. It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.
In this tutorial, we’re going to set up our file structure like below with a folder for reducers which we will talk about later on.
First things first, we need to install react-redux and redux’s library:
npm install redux
and npm install react-redux
and then let’s start our journey with index.js file.
index.js file:
Here, we have imported Provider Component
from react-redux
. What’s Provider? This is a context wrapper which we are going to use to wrap up our entire application and which receives the store as a property. Its sole purpose is to make Redux store
available to the rest of our App via Redux’s methods.
Now, let’s talk about Redux store
. The Redux store brings together the state, actions, and reducers that make up your app. Store is like an external state container where every React components can get access to it. Very important and to recall, you’ll only have a single store in a Redux application.
Create a store
In line 7, we have imported createStore
from redux
. CreateStore is a function that creates a Redux store
and will take as an argument reducer
function. In this time around, we have passed in rootReducer
. What’s rootReducer? it’s also a reducer function but for now, let’s put that question on hold and we will get back to it shortly. In line 10, we used createStore function to create our Redux store and assigned it to the variable store
. In line 13, we passed our store as props or property to the Provider Component, thus that Redux store would be accessible to every single component of our whole App via some Redux functions.
Reducers folder:
In this folder, there are two files: imageReducer.js and rootReducer.js.
What’s reducer: Reducer is a redux function that will be passed into createStore
as the argument. To recall, you could have numerous reducers but you can pass into createStore
only a single reducer function. We’ll cover this later on in this blog.
- imageReducer.js: In this file in line 1, we have just set an
initialState
because every reducer needs some initial state. In line 5, we have passed into ourImgReducer
function as arguments theinitialState
and theaction
. So far,ImgReducer
is the only reducer function for our App. To remember, reducers normally use ES6 default argument syntax to provide the initial state:(state = initialState, action)
.
2. rootReducer.js: In this file rootReducer.js
, the first thing we have done was to import ImgReducer
from “./imageReducer”. In here, we have another Reducer function which is reducer
. As stated above, we can only pass one reducer as an argument into createStore
function, since we have two reducers we will need another function that can combine our two reducers into one. For this reason, we have to import combineReducers
function from redux
. The combineReducers
helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore
.
In line 26, we have passed our two reducers ImgReducer
& reducer
into combineReducers
and assigned it to the variable rootReducer
and finally, we made that rootReducer
exportable.
Back to index.js file:
In line 8, we have imported rootReducer
inside index.js file. In line 10, we have passed rootReducer
as an argument into createStore
function and assigned it to our Redux store
. At this point, our Redux store
encompasses our state, actions, and rootReducer (combineReducers). And finally, we have passed that store as property to the Provider Component in order to make it reachable, throughout the whole application, to all components.
Count.js file:
How components get access to Redux store’s data?
React-redux provides us another function connect()
which components can use to get access to store’s data. The connect()
function connects a React component to a Redux store. It has two purposes: it will connect the component to store so that it gets data from the store and dispatch information to store. Let’s consider connect
as a connector or bridge between React component and Redux store.
Now let’s find out how we can connect our functional component Counter to our Redux store and get can data from it. First things first, we need to import connect
from 'react-redux'
(line2).
Connect
accepts four different parameters, all optional. By convention, they are called:
mapStateToProps?: Function
mapDispatchToProps?: Function | Object
mergeProps?: Function
options?: Object
In this example, we are going to use the first two: mapStateToProps
and mapDispatchToProps
.
mapStateToProps
It is a function and by convention, it will be the first parameter of the connect
function. Your mapStateToProps
functions are expected to return an object. This object, normally referred to as stateProps
, will be merged as props to our connected component which is here Counter component.
Since we are using connect()
to connect our Counter component to store, let’s name the parameter we have passed to mapStateToProps
as store and console.log
it (you can give whatever name you would like).
It turned out that our store encompasses our rootReducer
(ImgReducer
and reducer)
where our state is located. Since mapStateToProps
is expected to return state our goal here is to get access to state from rootReducer
and return it.
Accessing to State from our reducers:
Returning State: For this component we only need count
field for our Counter component and we know count is inside reducer
that will be easy to retrieve. As we know mapStateToProps
will return an object therefore let’s name the key
of this object count
and its value
will be store.reducer.count.
As stated above, whatever mapStateToProps
return will be a props for our Counter component. Let’s check it out inside the developer tools if there is any props. As you can see below our Counter component has now count
as props.
mapDispatchToProps
Conventionally, the second parameter connect()
takes is mapDispatchToProps
. To recall, your component will receive dispatch
by default for instance you do not provide a second parameter to connect()
.
If your mapDispatchToProps
is declared as a function taking one parameter, it will be given the dispatch
of your store.
Your mapDispatchToProps
functions are expected to return an object, each field of the object should be a function, calling which is expected to dispatch an action to the store. Whatever it returns will be merged as props to your connected component which here Counter component.
Dispatch
: it takes as argument action
. Actions are plain JavaScript objects that have a type
field. You can think of an action as an event that describes something that happened in the application. On top of that, actions
have also payload
field and we put any extra data needed to describe what’s happening into the action.payload
field. This could be a number, a string, or an object with multiple fields inside.
Since the return of mapStateToProps
and mapDispatchToProps
functions will be merged as props to your connected Counter component, let’s open up our DevTools and check it out. We can see below that our component has some props: count
, decrement
and increment
.
Let’s console.log inside our connected Counter component those props we have received from
mapStateToProps
andmapDispatchToProps
by using connect function.
rootReducer.js:
Let me walk you through our reducer function. It takes two arguments state
and action
. We used Switch case statement to check the condition, if action.type
is “inc” we want our reducer function to return the current state first and then update count
field by increasing it. In this example, we have used spread operator to make a copy of our current (…state)
.On the other hand, if action.type
is “dec”, we want our reducer to return the current state and update count
field by decreasing it. If none of those conditions are not met our reducer function will return by default the current state.
At this point, though, I hope you can see how Redux makes our life more easier by avoiding the so-called Prop Drilling which is to pass data from one part of the React Component tree to another by going through other parts that do not need the data but only help in passing it around
If you made it this far, thanks for reading!
PS: I shared below the ImgComponent
and it’s not going to be commented it since we have already covered the same process inside counter.js file throughcounter Component.
resources: https://redux.js.org/introduction/getting-started,
https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers,
https://react-redux.js.org/7.1/api/connect#connect,
https://react-redux.js.org/7.1/introduction/why-use-react-redux