You have finally decided that you are going to use React for your application and you are all pumped up to start development until that fateful moment when you realize that you need data from a different component & the complexity of passing that data through props is making your code & your life HELL.
In simple terms, Redux acts like a container to store the state of your javascript application which is accessible across all your components.
Imagine this, you have your item listing application & for each item a user can mark it as favourite which would not only update your list view but also a total favourites list in your header.
You might be thinking that’s pretty easy, I can just pass the favourite list as props and update the element. But what if that component is deeply nested in your application which makes passing props through parent child component extremely ugly & complex or worse where you can’t pass a prop to the component–for instance Route Components like this:
// ProductView component is rendered when custom product id route is navigated
<Route path="/product/:id" component={ProductView} />
You need a better, scalable solution.
Redux helps you to handle such scenarios gracefully.
Redux in itself is not bound to any UI framework, you can use it with Vue, React, Ember and a lot more. Having said that, it is only possible for a UI framework to use Redux through a UI binding library which ties Redux to your UI framework.
React Redux is the official Redux UI binding library for React
Before we see how we can implement react-redux for an application, we need to understand few terminologies
Store is a centralised place where all your application state sits. Consider it as your bank which holds all your cash. It looks something like this in Redux Dev Tools.
Let’s take our previous analogy of Redux Store acting as a bank, now you would definitely want to either withdraw or deposit money right? And what would you do? You would raise a request to do so. Action creators acts as those requests. They emit actions which encapsulates the changes you want to make in the Store.
Ok moving on…
Now once you have raised a request to deposit or withdraw something, you have to then submit that request to your bank manager (P.S-we are overlooking online banking here for a second :P). The bank manager knows exactly how to process that request, and if everything looks good, he/she will deposit or withdraw the amount requested, hand it over to you and update the details in your account to reflect the new changes.
This is what Reducers do for you in Redux. They take whatever action you pass, make a copy of the existing state, apply all your updates to the state & finally publish the updated state changes to the store.
Now as we know these building blocks a little better, it’s time to see how they come together in our application. I am going to keep this as simple as possible so you can understand the flow properly. We are going to create a simple list with 2 items either of which you can add to your favourite’s by clicking Favourite button and when you click again, the item will be removed from the favourite’s list.
Pre-requisite: You can setup a sample app using create-react-app. If at any point you feel stuck, you can refer to the source-code here:
npm install redux
npm install react-redux
npm install redux-devtools-extension
Once you have installed all the above dependencies in your React app, the next step is to setup Redux Store in your application.
Add the following lines in your main index.js file from where you render your using ReactDOM.
Don’t worry if you are not clear what each of these lines do yet, I’ll explain you shortly 🙂
import { createStore } from "redux";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./reducers";
const store = createStore(
rootReducer,
{ itemActions: { favourites: [] } }, // sets initial state
composeWithDevTools() // makes debugging through Redux Dev Tools possible
);
const rootElement = document.getElementById("root");
// Replace your existing ReactDOM.render() with below
ReactDOM.render(<Provider store={store}><App /></Provider>, rootElement);
Now let’s understand what exactly are we doing in the above code.
<App/>
, the only way to pass store instance to our entire application is through <Provider/>
component by react-redux. We pass a store attribute to this component whose value is our store instance.For our above example, we would be needing 2 action creators
It’s a good practice to keep all your action creators in one directory. So, let’s create a directory named actions/ inside src/
Next, create an index.js file with actions/ directory.
Within that we will write our 2 action creators.
import { MARK_FAVOURITE, REMOVE_FAVOURITE } from "../constants/ActionTypes";
export const markFavourite = name => ({
type: MARK_FAVOURITE, // mandatory key
name
});
export const removeFavourite = name => ({
type: REMOVE_FAVOURITE, // mandatory key
name
});
Creating action creator is just half the battle, we need something that understands those actions and updates our store. That is where Reducers come into picture.
Generally a real world application has multiple reducers, hence for clarity & better maintenance we should place all our reducers in a separate directory under src/ as reducers/
Next, let’s create our itemActions.js reducer file which will interpret our MARK_FAVOURITE & REMOVE_FAVOURITE actions.
const itemActions = (
state = {
favourites: []
},
action
) => {
switch (action.type) {
case "MARK_FAVOURITE":
return {
...state,
favourites: [...state.favourites, action.name]
};
case "REMOVE_FAVOURITE":
const favourites = state.favourites.filter(item => item !== action.name);
return {
...state,
favourites
};
default:
return state;
}
};
export default itemActions;
Note: The 1st argument of any reducer is the state which is initialised with default state for that reducer & the 2nd argument is the action which the reducer is going to interpret.- Within itemActions() exists a switch case which identifies what code should be executed for the give action type.
As real world application generally have more than one reducer, its necessary to pass all of them while we initialize the store. combineReducers() from redux precisely does that for you.
Let’s see how it’s done!!
First create an index.js file within your reducers/ directory
import { combineReducers } from "redux";
import itemActions from "./itemActions";
export default combineReducers({
itemActions
});
Remember the rootReducers import which we did while creating store in Step 2, this is where it’s imported from.
So far we have covered the creation part of react-redux application, now it’s time to see how we can invoke our action creators from our component & access this state from store.
In order for our component to have access to the Store & invoke action creators we would need some sort of binding function. connect() from react-redux does exactly that.
Let’s see how we can invoke this. Please refer to the complete code here:
...
import { connect } from "react-redux";
import { markFavourite, removeFavourite } from "../actions";
class ItemList extends React.Component {
// component code here
// action creators are now available in the props
updateFavourites = name => {
return this.props.favourites.find(item => item === name)
? this.props.removeFavourite(name)
: this.props.markFavourite(name);
};
}
const mapStateToProps = state => ({
favourites: state.itemActions.favourites
});
export default connect(
mapStateToProps,
{ markFavourite, removeFavourite }
)(ItemList);
connect()
Note – Don’t make a mistake of directly importing and using action creators functions. You need to pass them to a connect() which makes a dispatch object available to our action creator using which an action is dispatched to the reducer which in turn updates the store.
If you were able to be with me so far, Kudos to you.
Though mastering Redux has a learning curve, taking the first step without getting overwhelmed is very crucial. I hope this post helps you create your first tangible react-redux application 🙂
Originally published at https://programmingwithmosh.com
#reactjs #react-js #redux #webdev #javascript