Write a post

Build a google book explorer with React-Redux

Published Jun 07, 2017
Build a google book explorer with React-Redux

I remember a few months ago, I needed to learn how to write JavaScript frontend applications using react-redux. It wasn't an easy journey as the idea of using javaScript to write html looks totally weird to me and I could not find a decent beginner materials on this topic.

I ended up settling on corryhouse pluralsite tutorial on learning react and redux and it was really helpful.

So, I've decided to write a blog post about getting started with these technologies. In this tutorial, we're going to be build a book explorer using ES6 react-redux and google book api. A user will be able to search for books of choice and can be linked to the google page of each book. book explorer

By the end of this tutorial, you would have learnt,
(1) how to use webpack,
(2) how to create react-components
(3) how to navigate from one component to another
(3) differences between presentation and container components
(4) how to make ajax calls in react app
(5) how to start an express server
(6) how to use redux

Before we begin...

Let's take a moment to review the tools we're going to be using:

  • JavaScript - this post assumes the reader has a prior knowledge of javascript and can understand ES6 synthax

  • NodeJS - We're going to use this to run JavaScript code on the server. I've decided to use the latest version of Node, at the time of writing, so that we'll have access to most of the new features introduced in ES6.

  • ExpressJS - Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. we will be using express to create our node js server and to serve the entry point of our page.

  • webpack - according to thier website, webpack is a module bundler for modern JavaScript applications. When webpack processes your application, it recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles - often only one - to be loaded by the browser. So in other words instead of adding so many scripts to our html page, webpack will help us bundle our entire frontend javascript codebase into a single script to be included on our html page.

  • react.js -
    React is a UI library developed at Facebook to facilitate the creation of interactive, stateful & reusable UI components. It is used at Facebook in production, and Instagram.com is written entirely in React.
    I recomend you go through this getting started with react blog post by Ken Wheeler to understand more on react. Dont worry, I will wait for you.

welcome back,

You see, I am still here and your knowledge of react is even better. let us continue

  • redux - according to their website Redux is a predictable state container for JavaScript apps.

Redux Data Flow

Redux architecture revolves around a strict unidirectional data flow.
This means that all data in an application follows the same lifecycle pattern, making the logic of your app more predictable and easier to understand.

The data lifecycle in any Redux app follows these 4 steps:

redux life cycle.jpg

(1) In your component, when yo want to carry out an operation, You call store.dispatch(action).

(2) An action is a plain object describing what happened. For example:

 { type: 'LOGIN_SUCCESSFUL', userId: 42 }


{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }


{ type: 'ADD_TODO', text: 'Read the Redux docs.' }


{ type: 'SEARCH_SUCCESSFUL', booksFound: searchedBooks }

Think of an action as a very brief snippet of news. “search is Successful, the books found are searchedBooks which is probably an array or a collection of books matching the search .” or “‘Read the Redux docs.' was added to the list of todos.”

You can call store.dispatch(action) from anywhere in your app, including components and XHR callbacks, or even at scheduled intervals.

(3) The Redux store then calls the reducer function you gave it.

The store will pass two arguments to the reducer: the Initial state (see it as initial values in the store) and the action being called or (dispatched) e.g the object with LOGIN_SUCCESSFUL above. For example, in the the books explore app, the root reducer might receive something like this:

// The initial application state (logged-In user detail)
let previousState = {
  userId: 42,
  userName : 'freemile'

// The action being performed (search for books with harry)
let action = {
  booksFound: ['harry potter 1', 'harry potter 2', 'harry and john', 'harry the snake boy']

// Your reducer returns the next application state
let nextState = bookExploreAppReducer(previousState, action)

Note that a reducer is a pure function. It only computes the next state. It should be completely predictable: calling it with the same inputs many times should produce the same outputs. It shouldn't perform any side effects like API calls or router transitions. These should happen before an action is dispatched.

The root reducer may combine the output of multiple reducers into a single state tree.

How you structure the root reducer is completely up to you. Redux ships with a combineReducers() helper function, useful for “splitting” the root reducer into separate functions that each manage one branch of the state tree.

Here's how combineReducers() works. Let's say you have two reducers, one for a Login user, and another for the search:

 function loginReducer(state = initialState, action) {
   // Somehow calculate it...
   return nextState

 function searchReducer(state = 'initialState', action) {
   // Somehow calculate it...
   return nextState

 let rootReducer = combineReducers({

When you emit an action, rootReducer returned by combineReducers will call both reducers:

 let nextLoginState = loginReducer(initialState, action)
 let nextSearchState = searchReducer(initialState, action)

the combineReducer will then combine both sets of results into a single state tree:

 return {
   loginReducer: nextLoginState,
   searchReducer: nextSearchState

(4) The Redux store saves the complete state tree returned by the root reducer.

This new tree is now the next state of your app! Every listener/components that subscribe to the state will now be invoked; listeners may call store.getState() to get the current state. There is no way anyone can directly modify the store. The only way to do so is through reducers, and the only way to trigger reducers is to dispatch actions

Now, the UI can be updated to reflect the new state.

So as you may guess, the folder structure for the book explorer and for many react-redux app will look like this:

├── dist
├── src
│   ├── actions
│   ├── components
│   ├── reducers
│   ├── store
│   ├── styles
│   ├── index.html
│   ├── index.jsx   
│   └── routes.jsx
├── package.json
└── server.js
└── webpack.conf.js

(1) The dist folder is the folder that will contain our bundled file after webpack has bundled our source code.
(2) The index.jsx will be the entry point of our source code. every other file in the src folder are dirrectly or indirectly referenced in the index.jsx file.
(3) The routes.jsx file contains all the routes in the frontend of our application. It handles routing from one component to another.
(4) The server.js file is where we configure our express server to serve our page.
(5) The webpack.conf.js is the file that contains our webpack configuration.

Our package.json file will look like this

  "name": "bookexplorer",
  "version": "1.0.0",
  "description": "",
  "main": "index.jsx",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "babel-node server.js"
  "babel": {
    "presets": [
  "keywords": [],
  "author": "Ibrahim Usmam",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^3.19.0",
    "eslint-config-airbnb": "^15.0.0",
    "eslint-import-resolver-node": "^0.3.0",
    "eslint-plugin-import": "^2.2.0",
    "eslint-plugin-jsx-a11y": "^5.0.1",
    "eslint-plugin-react": "^7.0.1"
  "dependencies": {
    "babel-cli": "^6.24.0",
    "babel-core": "^6.24.1",
    "babel-loader": "^7.0.0",
    "babel-polyfill": "^6.23.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-es2016": "^6.24.1",
    "babel-preset-es2017": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "body-parser": "^1.17.1",
    "css-loader": "^0.28.4",
    "express": "^4.15.2",
    "extract-text-webpack-plugin": "^2.1.0",
    "html-webpack-plugin": "^2.28.0",
    "keymirror": "^0.1.1",
    "material-ui": "^0.18.1",
    "morgan": "^1.8.1",
    "path": "^0.12.7",
    "prop-types": "^15.5.10",
    "react": "^15.5.4",
    "react-bootstrap": "^0.31.0",
    "react-dom": "^15.5.4",
    "react-redux": "^5.0.5",
    "react-router": "^3.0.2",
    "redux": "^3.6.0",
    "redux-thunk": "^2.2.0",
    "style-loader": "^0.18.2",
    "webpack": "^2.2.1"

run npm install.

let us configure our express server.

Configuring our express server is a very easy task because we dont have much backend route as seen bellow .

import express from 'express';

// Set up the express app
const app = express();

// specify the entry point to our static file by 
// referencing the dist directory which contains
// our bundled file and the index.html file

//now we tell the express server to serve our 
// index.html for every requests( get, 
// put, delect or post)  made to any route 
// (* is a js regex expression that depicts any or all)
app.all('*', (req, res) => {

// set the port for the app to use
const port = process.env.PORT || 4000;
app.set('port', port);

// now tell the app to listen on the given port e.g
// (localhost:4000) which is the default port
app.listen(port, () => console.log('server started'));
export default app;

every other route will be handled by the frontend thereby making the browser to see our application as a single page application.

Next, we configure our webpack.conf.js to use our entry point and bundle our code. we will be using webpack 2 in this project.
the configuration file would look like this:


const webpack = require('webpack');
const path = require('path');

// the html-webpack-plugin configure's plugin to generate a 
// copy of our index.html file from the src directory,
// add a script tag referencing our bundle.js file  
// and include it in our dist directory

const HtmlWebpackPlugin = require('html-webpack-plugin');

// specify path to our index.html file in our src directory that we want to 
// include in the dist directory
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
  template: './src/index.html',
  filename: 'index.html',
  inject: 'body'

module.exports = {
  devtool: 'cheap-module-eval-source-map',
  entry: './src/index.jsx',
  module: {
    loaders: [
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['react', 'es2015']
        test: /\.css$/,
        loaders: ['style-loader','css-loader'] 
  target: 'web',
  resolve: {
    extensions: ['.js']
  output: {
    path: path.resolve(`${__dirname}/dist`),
    filename: 'bundle.js'


the webpack configuration consist of an object with various keys that serve different purposes and we set their values to suit the needs of our application.

devtool : this specifies the devtool that will be used to debug our app at the frontend. because our scripts have been bundled into one file, we wouldn't want all our errors to be unspecific and be pointing at bundle.js . We would want an error log that actually points to file and spot where the error is coming from.

entry : offcourse this is the entry point of our codebase. Every other file under the src folder are in one way or the other referenced in this entry file so we specify the entry file here

module : this specifies all the file types referenced in our entry point and the approiprate loader to be used for each file type. Each loader are pre-installed from the npm library. The test key specify a regex matching a file type e.g(/.jsx?$/) means files matching .js or .jsx and babel-loader is used for such file. Babel also helps to transpile es6 code down to es5 so that the browser can understand it.

target : Because JavaScript can be written for both server and browser, webpack offers multiple deployment targets that you can set in your webpack configuration. So we use web because our target is the frontend. We can also set it to node if the target is the backend.

resolve : Webpack uses resolve.extensions to generate all the possible paths to the modules being bundled.

output : Finally we specify the output path for our bundled file and the name we want it to have.

It seems we are all set, let us create our first component.......

There are two types of component
presentation component and container components

A container component is a react component that has direct connection with state managed by Redux and also called 'smart component'.

simple/ presentational is a component which only render the DOM elements to the screen and they are also called Dumb component. They are not aware of redux or store. they do not manipulate data, they only collect data as props and render it in the view. To read more on presentational and container components, visit here.

Our first component is a presentation component, it will collect the results of the searched books and render/ display it for the user.

inside components folder, create a file gallary.js.

The components looks like this:


import React from 'react';

 * @class Gallary
 * @extends {Component}
class Gallary extends React.Component {

    const alternate = 'http://www.51allout.co.uk/wp-content/uploads/2012/02/Image-not-found.gif'
    return (
          this.props.items.map(item => {
          let {title, imageLinks, infoLink} = item.volumeInfo;
            return (
              <a key={item.id} className= "book"
                target= "_blank">
                <img className="book-img" src={ imageLinks !== undefined ? imageLinks.thumbnail : alternate}
                     alt="book image"/>
                <div className="book-text">


export default Gallary;

as seen above:
(1) We import react from react.
(2) We create our component class (Gallary) and make it inherit from React.Component class.
(3) The render method is part of the method our component inherits from React.Component class and it is use to render JSX(html-like) component. I recomend you go through this getting started with react blog post by Ken Wheeler to understand more on react if you haven't done so and you dont know what JSX is .
(4) Now in our jsx component, we assume a prop item is been passed down to this component. the component doesn't know where and how this props was generated. All it knows is that it has a prop (items) which is an array of books and it has to display them. the props is been referenced with this.props.items .
(5) Now we loop through this collection of items and write the html code to display each of them so for every book, it does this:

// wraps the book in a link that leads to its google page with infoLinks gotten 
// from each item's object
// display the books image if image is available or use the specified 
// alternative image if the image is not available
// finally we display the books title
      <a key={item.id} className= "book"
                target= "_blank">
                <img className="book-img" src={ imageLinks !== undefined ? imageLinks.thumbnail : alternate}
                     alt="book image"/>
                <div className="book-text">

You can observe how we used className in JSX inplace of class in normal html. this is because class is a reserved keyword in javascript and since jsx is written in javascript hence the use of className to depict html class.

We can also import and use components written by other developers and use it in our application. Just like in our second component, the progress bar. we will import it from material ui.

Create another component under the components folder

import React from 'react';
import CircularProgress from 'material-ui/CircularProgress';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

const CircularProgressBar = () => (
      <CircularProgress size={80} thickness={5} />

export default CircularProgressBar;

You can read more on this component from here.
We display this component when we are handling an async operation and we dont want to leave the user clueless about what is happening.

Our final and last component is a container component. This component is aware of redux and the store and it also encompasses other components.

Create another component under the components folder

import React from 'react';

//connect is used to connect our components to the
// redux store so that it can get state of data or
// dispatch an action to modify data
import { connect } from 'react-redux';

// here we import reusable ui components from react-bootstrap
import { FormGroup, FormControl, InputGroup, Glyphicon } from 'react-bootstrap';

// import our gallary component that formats the books display.
import Gallary from './gallary.jsx';

// here we import search action from our action folder
import searchAction from '../actions/searchAction';

//finally we import our progress bar component
import CircularProgressBar from './progress.jsx';

 * @class Global
 * @extends {Component}
class Global extends React.Component {

   * Creates an instance of CreateDocument.
   * @param {any} props 
   * @memberof CreateDocument
  constructor(props) {
    this.state = {
      query: '',
      items: []
    this.handleChange = this.handleChange.bind(this);

    * @param {any} event 
    * @return {void}
    * @memberof Global
      this.setState({ query: event.target.value })

   * @return {void}
   * @memberof Global
  // calls the search method passing in the search query as an argument

   * @returns {void}
   * @memberof Global
    return (
      <div className="Global"> 
        <h2> Book explorer !</h2>
            <FormControl type="text" 
              placeholder="Search for a book"
              onKeyPress={event => {
                if(event.key === "Enter"){
            <InputGroup.Addon onClick={() => this.search()}>
              <Glyphicon glyph="search"/>
        { this.props.items?
        // here we say, if this.props.items is available, 
        // pass it to gallary component and render the 
        // gallarry component, otherwise, dont render anything here
        <Gallary items={this.props.items}/>
        : '' 

          this.props.isLoading ?
          // also here we say, if this.props.isLoading is true, 
          // render the progress bar component, otherwise, 
          // dont render anything here
          <div id="progress">
            <CircularProgressBar />
          : ''

const mapStoreToProps = (state) => {
  return {
    items: state.searchReducer.items,
    isLoading: state.loadingReducer.isLoading,
const mapDispatchToProps = (dispatch) => {
  return {
    search: query => dispatch(searchAction(query))

export default connect(mapStoreToProps, mapDispatchToProps)(Global);

as described above :
(1) We import all the required module .
(2) We set the initial state of query and items to empty string and empty object respectively.
(3) We format our display with jsx and our component in the render method
as shown above, our render method consist of a form with input fields

  • the form control has an onChange method that listens to changes in the input field and whenever this event occurred, the this.handleChange method is invoked
  • it also listens to the keypress of the enter key with onKeyPress then it invokes the this.search method
  • finally the glyphicon icon subscribes to the onClick event and also invokes the this.search method
    our form field will look like this Screen Shot 2017-06-05 at 10.56.31 PM.png.

(4) There are two class methods, the handleChange method and the search method.

  • The handleChange method set the this.state.query value to the value of what is in the input field whenever a user types in it.
  • The search method invokes the this.props.search method whenever the user clicks submit or presses enter key, and subsequently an action is dispatched, In other to update the store about this new event. The this.props.search method is an actionCreator that is passed to this component via the mapDispatchToProps function.


mapDispatchToProps is a utility which will help your component to fire an action event via react-redux::connect (dispatching action which may cause change of application state). Basically it connects the component with the actions via react-redux::connect. In the case of this project, it connects the component with the method that will inturn search for the books and dispatch the appropriate action.

mapStoreToProps / mapStateToProps

The mapStateToProps function has the Store state (which contains the entire rootReducer data) as an argument/param (provided by react-redux::connect) and it is used to link the component with certain part of the store state. i.e it takes some data from the store via the connect e.g(the new value of our items after the search is succesfull) and make it available to the component since it subscribes to that state. The component will then access this data via this.props.items and then use it in the render method thus mapStateToProps (meaning get state from the store and pass it as a prop).

Finally connect is used to connect our components to the redux store so that it can get state of data or dispatch an action to modify data hence.

connect(mapStoreToProps, mapDispatchToProps)(Global);

We can add stylings to our component.
create a styles.css file under styles

    text-align: center;
    padding: 10px;

.book {
    display: inline-block;
    width: 220px;
    height: 220px;
    margin: 10px;
    text-align: left;
    cursor: pointer;
    position: relative;

.book-img {
    width: 220px;
    height: 220px;
    border: 4px solid black;
    border-radius: 4px;
    object-fit: cover;
    position: absolute;

.book-text {
    background-color: black;
    color: white;
    opacity: 0.75;
    width: 214px;
    margin-left: 3px;
    position: absolute;
    text-align: center;
    bottom: 0;
    padding: 10px;


#progress {
  top: 50%;
  	left: 50%;
  	position: absolute;
  	transform: translate(-50%,-50%);


Now that we have our components ready, let us setup our frontend routes .
The routes.jsx file will be handling this for us as indicated in the folder structure above.

import React from 'react';
import { Route } from 'react-router';
import Global from './components/global.jsx';
// this is basically telling our router to render
// our Global component when the path "/" is visited  
export default(
    <Route path="/" component={Global} />

larger applications usually have more than one route to render various components and all the routes can be arranged like this

    <Route path="/" component={Global} />
    <Route path="/users" component={Another Component} />
    <Route path="/winners" component={Another Component} />


When the user clicks on enter, the search method is called. The search method is responsible for fetching the books from our api google books api and subsequently dispaching the search action.
The action folder consist of 3 files,

  • the varrious actiontypes
  • the search action and
  • the loading action

create this files in the action folder.

  • actionTypes.js this is an object contating all available action types
// keymirror Creates an object with values equal to its key names.
import keymirror from 'keymirror';

export default keymirror({
  LOADING: null,
  • loadingAction.js
export default {
    isLoading: (dispatch, actionTypes) => {
      return  dispatch({
        type: actionTypes.LOADING,
        isLoading: true,
    isNotLoading: (dispatch, actionTypes) => {
      return  dispatch({
        type: actionTypes.NOT_LOADING,
        isLoading: false,

loadingAction.isLoading dispatches the LOADING actionType and set isLoading to true when a page is loading so the progress bar component will be rendered for the user to know what is happening.

While loadingAction.isNotLoading dispatches the NOT_LOADING actionType and set isLoading to false when a page has finish loading so the progress bar component will be hidden.
The two loading actions were not dispatched in the components as show above but instead they are dispached in the searchAction before and after the asynchronus fetch operation.

  • searchAction.js
import actionTypes from './actionTypes';
import setLoading from './loadingAction';

export default (query) => {
  return (dispatch) => {
  // notifies the component that the page is loading
    setLoading.isLoading(dispatch, actionTypes);
    const BASE_URL = 'https://www.googleapis.com/books/v1/volumes?q=';
    // here we used fetch to make an ajax call to the google api
    // fetch is an inbuilt es6 method for making ajax request.
    // we can also use external libraries such as **axios** and **request**
    fetch(`${BASE_URL}${query}`, { method: 'GET'})
      .then(response => response.json())
      .then(json => {
      // notifies the component that the page is has finish loading
         let { items } = json;
         // dispatches the SEARCH_SUCCESSFUL action with the new items that was 		 //	fetched
           type: actionTypes.SEARCH_SUCCESSFUL,
           status: 'success'
      .catch((err) => {
       // notifies the component that the page has finish loading
         // dispatches the SEARCH_UNSUCCESSFUL action with the error message
           type: actionTypes.SEARCH_UNSUCCESSFUL,
           status: 'failed',
           error: err.message


The reducer inturn listens to the action types that is being dispatced and then update the store appropriately.

The reducer folder contains 3 files:

  • index.js which is the rootReducer that combines all other reducers
import { combineReducers } from 'redux';
import searchReducer from './searchReducer';
import loadingReducer from './loadingReducer';

const rootReducer = combineReducers({

export default rootReducer;
  • loadingReducer.js this listens to the action being dispached using switch cases and update the store with the new state.
import actionTypes from '../actions/actionTypes';

 * @export
 * @param {any} [state=initialState] 
 * @param {any} action 
 * @returns {state}:
export default function loadingReducer(state = { isLoading: false }, action) {
  switch (action.type) {
    case actionTypes.LOADING:
    // updates the store with the new state
      return Object.assign({}, state, {
         isLoading: action.isLoading
    case actionTypes.NOT_LOADING:
     // updates the store with the new state
    return Object.assign({}, state, {
         isLoading: action.isLoading
     // if the action is not handled here, return the previous state
      return state;
  • similarly for searchReducer.js, takes two argument previous state and actions then update the store depending on the dispatched action
import actionTypes from '../actions/actionTypes';
import initialState from '../store/initialState';

 * @export
 * @param {any} [state=initialState] 
 * @param {any} action 
 * @returns {state}:
export default function loadingReducer(state = initialState, action) {
  switch (action.type) {
    case actionTypes.SEARCH_SUCCESSFUL:
      return Object.assign({}, state, {
         items: action.items,
         message: action.message
    case actionTypes.SEARCH_UNSUCCESSFUL:
    return Object.assign({}, state, {
        status: 'failed',
        error: action.message
      return state;

As observed above, we try not to overwrite the previous state in the reducer with the new one. we therefore create a copy of the old state and update that copy with the new one with the Object.assign method.
This is because Redux was originally invented to demonstrate the idea of "time-travel debugging" - being able to step back and forth through the history of dispatched actions, and see what the UI looks like at each step. Another aspect is being able to live-edit the code, reload it, and see what the output looks like given the new reducer logic.

The redux store
As explain earlier, The Redux store saves the complete state tree returned by the root reducer.
our application store folder contains two files

  • initialState.js this is the initial state at the start of our application, it is usually an empty object or an empty array
const initialState = {};

export default initialState;
  • configureStore.js the store folder also contains a configuration for our store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import rootReducer from '../reducers';

// configure middleware
// Redux reduxImmutableStateInvariant is a middleware
// that spits an error on you when you try to mutate 
// your state either inside a dispatch or between 
// dispatches. For development use only!

// Redux Thunk middleware allows you to write 
// action creators that return a function instead
// of an action. The thunk can be used to delay the
// dispatch of an action, or to dispatch only if a certain condition is met.

const middlewares = [thunk, reduxImmutableStateInvariant()];

 * @export
 * @param {any} initialState 
 * @returns {void}
export default function configureStore(initialState) {
// creates the store and redux handles every other thing for you behind the scene
  return createStore(

Finally we create the index.html file and the index.jsx entry point file.

<!DOCTYPE html>

      <base href="/" />
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  		<!-- our final react component will be attached to this id-->
      <div id="root"></div>


All the files under the src folder are referenced here and it is here we wrap our store provider around the app's main component and it is then attached to the html page with the render method

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router'
import routes from './routes.jsx';
import configureStore from './store/configureStore';
import initialState from './store/initialState';
import './styles/styles.css';

const store = configureStore(initialState);

  <Provider store={store}>
    <Router history={browserHistory}>{routes}</Router>
 , document.getElementById('root'));

This was a long post but yay! We're done! Well, almost. 😃
on the terminal
run :

  • npm run build, after bundling the app successfully, you should see a dist folder consisting of bundle.js and index.html file.
  • npm start, to start the app
  • navigate to localhost:4000 in your browser to use the app.

feel free to improve the app by adding new functionalities
You can find the complete code in this GitHub repo.

I hope this helped you to get up and running with react-redux and webpack.

Discover and read more posts from andela-uibrahim
get started
Enjoy this post?

Leave a like and comment for andela-uibrahim

Be the first to share your opinion

Subscribe to our weekly newsletter