Intermediate Step-by-Step Tutorial Using React, Redux and Redux-Saga: Part 2

Johannes Neumann
9 min readJun 27, 2017

--

This is part 2 of the step-by-step tutorial using React, Redux and Redux-Saga, you can find part 1 here. In part 1 we designed our application, in part 2 we will start building an app that has most of the functionality of the end result, but contains only hard coded data.

What are we building?

The application we are about to build!

What does our architecture for the hard coded app look like?

Application architecture will be implemented in part 2 and part 3 of this tutorial series

Building the Static App

Although it took quite a while we are finally ready. Even though this is a small app, planning the design carefully without writing a single line of code is extremely helpful. We now have a good idea of what we have to code up.

Create React App

To start our project we will be using the always handy create-react-app. This way we can entirely focus on React without having to worry about setting everything up with Webpack. Just as the docs say create-react-app lets you ‘focus on the code’.

In case you never used create-react-app install it globally first

npm install -g create-react-app

And then create the App, this process might take a few minutes.

create-react-app country-picker

When your App is ready make sure it works

cd country-picker
npm start

When you now navigate to http://localhost:3000/ you should see a spinning react logo with the text ‘Welcome to React’.

Next delete all files under the src folder so we can start fresh.

Linters (optional)

Linters help you follow guidelines on how your code should be structured. If you don’t want to use a linter for such a small project that’s fine. However, in the spirit of completeness I wanted to mention that the app on GitHub uses the following linter packages:

"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-plugin-import": "^2.3.0",
"eslint-plugin-jsx-a11y": "^5.0.3",
"eslint-plugin-react": "^7.0.1"

Depending on the IDE you use the linter setup will be a bit different. However you will always need to add a .eslintrc.* file.

CSS

Alright let’s get the CSS part out of the way first. This tutorial is not about CSS so I will just give you the bit needed to make the App look somewhat presentable. Additionally we will be using the bootstrap.css file. But we will not be using any CSS preprocessors such as SASS.

Go to the public folder of your App and under the <head>section paste the bootstrap CDN link for its CSS file. You can find the CDN link here.

Now replace the content of src/index.css (or create a new index.css if you followed my instructions and deleted all files) with the following:

cd src
touch index.css

That is all the custom CSS we need. All other CSS classes will come from bootstrap.

npm packages

For this part of the tutorial we need four dependencies / packages which aren’t installed by default.

  • prop-types: React moved it’s prop types into a separate package. Prop types mainly serve as a way to validate our props during runtime. The bigger your app gets the more valuable they become.
  • redux: Well since we are using Redux we need to install it
  • react-redux: Exposes bindings which help to use Redux together with React
  • redux-logger: Logs the state of our store, which is very useful for debugging

We need to install four of these packages

npm i -S prop-types redux react-redux redux-logger

index.js

We can now start creating our components. Our strategy will be to follow the architecture diagram from top to bottom. Let’s start with the entry point index.js.

touch index.js
index.js

Here we pull in our css file and place the AppContainer component into the div element with the id root. Now we have to actually create the AppContainer

AppContainer

We first create a folder for our containers and then add an AppContainer.js file.

mkdir containers && cd containers 
touch AppContainer.js
cd ..

The first responsibility of the AppContainer is to create the store. To do so we create a function called createStore , which takes the initial state of the store as an argument, that state is an empty object in our case.

// function to create initial store setupconst configureStore = (initialState) => {
const enhancers = applyMiddleware(logger);
return createStore(rootReducer, initialState, enhancers);
};

We will create the rootReducer later, for now know that we need to pass all our reducer functions to the store as part of the initial setup.

Lastly the AppContainer needs to wrap the AppLayout into a provider so that all of its children can access the store if necessary. Here is the code for the complete component:

containers/AppContainer.js

AppLayout

Next is the AppLayout component. First create another folder called layouts and then create the AppLayout.js file.

mkdir layouts && cd layouts
touch AppLayout.js
cd ..

The purpose of this component is to organize the layout of our app, if we had a header or footer we would include them here. Additionally we use some bootstrap classes in order to create two columns side-by-side:

layouts/AppLayout.js

Reducers

The next component would be the MasterViewContainer, however as you can see from the diagram that container accesses the store. Therefore, we need to start implementing our reducer first.

Create a reducer folder containing the following files: countriesReducer.js and index.js.

mkdir reducers && cd reducers
touch countriesReducer.js
touch index.js
cd ..

Let’s start with our countriesReducer. For now we will create a reducer which has an initial state that contains an array countries containing three country objects and a string selectedCountry that is equal to one of the names of the country. We will use the name of a country here instead of creating an extra id.

reducers/countriesReducer.js

As we saw in the Redux cycle diagram we need to dispatch an action before the reducer does anything. In our case this action object needs to have a type property of SET_SELECTED_COUNTRY and once that action is dispatched our reducer will replace the current selectedCountry string with a new name.

Before we create that action we need to fill in index.js, which we created at the start of this section. The sole job of the file is creating a rootReducer, which combines all our reducers so the store can be initialized. This might seem unnecessary since we only have one reducer, but in a larger application you will have many reducers and in fact you will most likely combine reducer more than once.

reducers/index.js

Actions

Now we need to create our action. Create an actions folder containing two files: index.js and types.js

mkdir actions && cd actions
touch index.js
touch types.js
cd ..

Our index.js contains all our actions. The action types are stored in types.js. We store action types as separate constants because we need to use them in multiple files.

actions/index.js
actions/types.js

Checkpoint: At this point you should be able to comment out MasterViewContainer and DetailViewContainer in AppLayout.js and your App should be running without any errors.

MasterViewContainer

Now that we finished our store, reducers and actions, it’s time to connect to the store and get some data. First create the MasterViewContainer:

cd containers
touch MasterViewContainer.js
cd ..

As described in the diagram the MasterViewContainer connects to the redux store and passes down props to the MasterViewPage.

In order to access the props we use the mapStateToProps function to access the countriesReducer directly. Notice, this means we only access certain reducers and even within those reducers we only access certain data.

const mapStateToProps = state => ({
countries: state.countryReducer.countries,
selectedCountry: state.countryReducer.selectedCountry
});

Our container also needs to access the action, which we can then pass down as a function. Here we have different possibilities, we could just pass the dispatch object down as a props to child components and give them the ability to call any action. However, it would be nicer if the child components could only dispatch actions they actually need. Another issue is that we do not want non-container components to be aware of Redux. To those components an action should look like a function, which was passed as a prop. Therefore we are going to use so-called action creators.

We are going to create an actions object using the mapDispatchToProps function and by using the bindActionCreators helper.

import * as actions from '../actions';const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(Object.assign({}, actions), dispatch)
});

Let’s go through what this code does

  • import * as actions from '../actions': we import all our action functions (note that in a larger app we would have many different action files and would only import the action files we need)
  • Object.assign({}, actions) : We create a new object which contains our actions
  • bindActionCreators(Object.assign({}, actions), dispatch: We wrap every action into a dispatch call. This enables us to pass them down as functions to our child components.

Finally we have to connect to the store by using connect from the react-redux package. We use the two functions we defined to tell the store what we want and then reference our component. The objects created by the mapStateToProps and mapDispatchToProps are passed as props to the connected MasterViewContainer component.

connect(mapStateToProps, mapDispatchToProps)(MasterViewContainer);

The rest of the MasterViewContainer is nothing special. We just call the MasterViewPage and make sure that we only render it if there are actually countries present (this will become important once we fetch data from an API instead of hard coding it).

containers/MasterViewContainer.js

MasterViewPage

First create a new folder called pages containing the MasterViewPage.js file.

mkdir pages && cd pages
touch MasterViewPage.js
cd ..

The MasterViewPage component will hold the search bar and the list view. However, since we will be implementing the search bar in part 2 of this tutorial series we will use this component for now to display the list view directly.

pages/MasterViewPage.js

As we can see the list item will be active depending on the selectedCountry string, which comes from the store. Once that string changes the store will have a new state and trigger an UI update. In the spirit of reusability we named the props for the ListItem component independently of our app. Also notice that the handleOnClick function does not have to be Redux related. We could truly take the ListItem component and plug it into a completely different React application without adapting it.

ListViewItem

The ListViewItem is the first component, which is neither a container nor a page, therefore we will create a new folder called components containing ListViewItem.js

mkdir components && cd components
touch ListViewItem.js
cd..

The ListViewItem just displays its props accordingly:

components/ListViewItem.js

Summary

Our App now displays a list of countries (which we hardcoded) and when clicking on a country it gets highlighted.

Done with part 1!

Seriously? This tutorial was so long and that is all we achieved?

Not really, we achieved a lot more. We built the foundation for a scalable application using Redux. Yes, Redux takes a while to setup but in the long run it can make development a lot easier. We now have a solid foundation to expand our app. In our console (image uses chrome dev tools) we can see that when we click on a list item a new action gets dispatched, leading to a change in state.

Action dispatched to change selectedCountry from ‘France’ to ‘Bulgaria‘

Further we can inspect the state and see our countriesReducer and its countries array, which includes our hard coded three countries and the selectedCountry string.

In the next part of this series we will complete the application using hard coded data. That means we will add a search field and build out the detail view. In part four we will then connect to an API using redux-saga and in part five we will include tests.

Checkpoint

Your folder structure inside your src folder should look like this:

You can find the version of part 1 in my github repository.

https://github.com/Joe312341/country-picker-tutorial/tree/master/country-picker-part-1

If you find any bugs please let me know in the comments!

--

--