Starting out with react-redux-starter-kit 3.0.0-alpha.2

This is a small tutorial designed for people who are baffled with the react-redux-starter-kit and don't know where to start. The idea was originally spawned from this github issue.
I'll go (briefly) through all the main components included, and in the end we'll build something nice, showing how the pieces actually fit together.

I should probably note that since the starter kit is strongly on the bleeding edge side of the things, some experience with front-end development is probably necessary to follow along with this tutorial. I'll try to provide links on all the topics, so a lot of it can be obtained on the go. :)

Important: This is an update of the tutorial written in April 2016 by Markus Klinga. Markus's original article is based off of 3.0.0-alpha.0. As there have been a number of changes to the react-redux-starter-kit since that article was written, I have taken the opportunity to update Markus's original tutorial to reflect the current release of the react-redux-starter-kit. Other than code updates and minor grammatical fixes, the text has remained unchanged from the original as much as possible. Thanks again to Markus Klinga for the excellent original tutorial, and to the entire team behind the react-redux-starter-kit project.

Versions we'll be using during this tutorial:

  • node.js: v6.9.0
  • npm: 4.0.5
  • react-redux-starter-kit: 3.0.0-alpha.2

So what's in the box?

As it turns out, there is a lot of functionality provided. Let's start by going through the features list (as of the writing of this blog post) one-by-one, and find out all the main components being used.
If you're new to these tools, I'd advise you to take your time to get at least a basic understanding about what the individual components do, and why they're provided -- there's a lot to chew on here, but in the end everything has its place.

React (^15.0.0)

React is the given champion of this starter kit. It is used as the V in the traditional MVC model, and it provides a simple way to generate reusable components within JavaScript. If you're new to React, a great place to start is at the official documentation by facebook.

Redux (^3.6.0)

The starter kit comes preloaded with Redux, react-redux, and redux-thunk. These are the main building blocks that should guide the structure and the data flow of your application.
Redux is a predictable state container for JavaScript apps. It is used to hold the application state inside a single store. The only way to change the state is through actions that are objects describing what has happened, and reducers, functions that specify how the actions transform the application state. If you haven't yet read the offical Redux documentation
The react-redux package provides us with the bindings for using Redux with React. In practice, it gives us a connect() function and a provider() component. Later on, we'll dive into both of these within our example.
As recommended in the official documentation, this starter kit also embraces the idea of separating presentational and container components.
Finally, there is a redux-thunk middleware that allows us to better deal with asynchronous actions within Redux by dispatching thunks. For more information and code examples about how thunks work, check out the redux-thunk README.
If you're interested in knowing why redux-thunk was created, there as a great post available at StackOverflow explaining just that.

react-router (^3.0.0)

As one might guess, react-router combined with react-router-redux handles all the routing in our application. It provides an API that contains pretty much everything one could ever need to keep the application's URL and UI consistent with each other.

webpack (^1.12.14)

Webpack is a module bundler. What this means is that webpack allows us to import basically all the assets we would want to use with our components -- including JavaScript modules, styles, fonts or even images. A very good about what webpack is can be found here.
To find out more, check out the official documentation.
React-redux-starter-kit also uses webpack for hot module replacement. Hot module replacement allows us to develop with the results instantly patched onto the browser without the need to explicitly refresh the page. The webpack config file can be found within the project at ./build/webpack.config.js. It comes preloaded with Sass w/ CSS modules, autoprefixer, and minification.

express (^4.14.0)

The starter kit comes with a version of the express server that is not meant to be used in production out of the box. You can build upon the server, but that goes beyond the scope of this starter kit. To learn more, see the project's deployment notes at ./README.md.

Code quality

There are some code quality tools readily available within the react-redux-starter-kit.

Karma, a spectacular test runner, is used to run the tests. Check out ./build/karma.conf.js for test configurations.

Mocha test framework with chai, sinon-chai, chai-as-promised, and chai-enzyme for various assertions.

PhantomJS headless webkit server, which allows us to mock the browser environment without having to launch a real browser.

Code coverage reports/ instrumentation with isparta.

ESLint for code linting. You can see and modify the linting rules at ./.eslintrc.

We won't go into anymore detail with these, but we do encourage you to use all the provided tools to improve your project's quality.

babel-core (^6.17.0)

Babel is a widely-used JavaScript compiler that allows us to write code using the latest ES6 features (and more), transforming the code to be ES5-compatible in the build phase using various plugins.
The react-redux-starter-kit comes with the following presets ("collections of plugins"):

  • ES2015
  • React
  • Stage-0

There is also the babel-plugin-transform-runtime plugin installed.
You can see the babel configuration at line 33 in config/project.config.js.

All right, how do I use this?

Now then! We should have a basic understanding about most of the pieces included, so what do we do now?

We start playing with it, of course!

Did you know that Github offers and API for Zen quotes? Me neither. Let's build something out of that.

First things first -- if you haven't yet installed nodejs, npm, and git, now would be a great time. After that is done, you can clone the repository and install the required packages.

In your terminal, type:

git clone git@github.com:davezuko/react-redux-starter-kit.git  
cd react-redux-starter-kit  
npm install  

After it is installed, to test that all is well, type:

npm run start  

After a while, our application should be running at https://localhost:3000. Neat!

Zen

A good way to start implementing things is to figure out what we actually want to do. I think we would like to do a small page where:

  • user can fetch a wisdom from Github
  • user can "refresh" the wisdom, aka fetch another one
  • user can "save" his/her favorite ones on a list that is shown below the latest

With that, I think our users would be quite happy.

Modifying the original codebase

Let's start by the changes we need to do on the existing codebase.
At first, we need to add a new childroute to the route index by requiring our Zen route just like the existing Counter is required.

./src/routes/index.js
// ... omitted for brevity

export const createRoutes = (store) => ({  
  path: '/',
  component: CoreLayout,
  indexRoute: Home,
  childRoutes: [
    CounterRoute(store),
    ZenRoute(store)
  ]
})

As with the existing Counter component, we provide the store as a parameter to the Zen childRoute. This is important, because we want to attach reducers asynchronously to the store only when the Router actually invokes the callback.
The second thing that we change is more cosmetic -- we simply add a link to the route from the <Header> component. Add the following snippet after the other in the file:

./src/components/Header/Header.js
// omitted for brevity
<Link to='/counter' activeClassName='route--active'>  
  Counter
</Link>  
  {' · '}
<Link to='/zen' activeClassName='route--active'>  
  Zen
</Link>  

And that's it! Now we have injected a new route and added a link to this route (/zen). From now on, we can concentrate on our own component without having to change anything else from the existing code base.

We will place our code at ./src/routes/Zen, and in the end we will end up with the following structure:

src/routes/Zen  
├── components
│   ├── Zen.js
│   └── Zen.scss
├── containers
│   └── ZenContainer.js
├── index.js
├── interfaces
│   └── zen.js
└── modules
    └── zen.js

If you're following along, please just create the subdirectories/ files when necessary. You can find the whole source code at the zen-example repository.

Route

The react-redux-starter-kit allows the use of the fractal app structure that gives us an option to define units of logic by our route definitions. This is very useful in larger applications, but for smaller applications (like our example), it's quite optional.
So, since we have already added our child route at ./src/routes/index.js, it is now a good time to actually create that route. Each child route must have an index.js, which exports its route definition.

./src/routes/Zen/index.js
import { injectReducer } from '../../store/reducers'

export default (store) => ({  
  path : 'zen',
  /*  Async getComponent is only invoked when route matches   */
  getComponent (nextState, cb) {
    /*  Webpack - use 'require.ensure' to create a split point
        and embed an async module loader (jsonp) when bundling   */
    require.ensure([], (require) => {
      /*  Webpack - use require callback to define
          dependencies for bundling   */
      const Zen = require('./containers/ZenContainer').default
      const reducer = require('./modules/zen').default

      /*  Add the reducer to the store on key 'zen'  */
      injectReducer(store, { key: 'zen', reducer })

      /*  Return getComponent   */
      cb(null, Zen)

    /* Webpack named bundle   */
    }, 'zen')
  }
})

Here, we export a function to react-router which then only requires our component and the reducer when the router actually tries to show them, instead of requiring our component and the reducer during the initial load of the application. This helps us to keep the loaded codebase much smaller, and thus faster to load.
We also use the provided injectReducer function to dynamically inject our reducer to the store (with a key of zen). Note that we could as well have multiple reducers on our component, and use the combineReducers function from redux to send them to injectReducer function.

State

To achieve the goals we stated in the description of our component, we need to store the following things in our application state:

  • List of fetched wisdoms
  • The id of current wisdom
  • Information whether we are currently fetching another one
  • List of id's for the wisdoms the user has saved

This is quite the minimum implementation of the application, and we leave it as an exercise for the reader to improve upon this!
We will save these fields under a common zen key, so the final implementation of the state will look something like this (omitting router and counter-specific states from the existing code):

state: {  
  zen: {
    current: 2,
    fetching: false,
    zens: [
      { id: 0, value: "..." },
      { id: 1, value: "..." },
      { id: 2, value: "..." }
    ]
    saved: [
      0,
      1
    ]
  },
  (...)
}

This model states that we store all the fetched wisdoms in the zens array, giving them unique ids. We then use these ids to reference the zens from the saved list, as well as the current zen.
This is a good way of dealing with values that are referenced from multiple places and that may change over time.

Actions

For our Zen, we will defined the following actions:

  • REQUEST_ZEN
    • dispatched when the request is launched
  • RECEIVE_ZEN
    • dispatched when the request is fetched
  • SAVE_CURRENT_ZEN
    • dispatched when user wants to save the current wisdom

We will define these (piece by piece) at ./src/routes/Zen/modules/zen.js. You can find the whole source file here:

src/routes/Zen/modules/zen.js
// ------------------------------------
// Constants
// ------------------------------------
export const REQUEST_ZEN = 'REQUEST_ZEN'  
export const RECEIVE_ZEN = 'RECEIVE_ZEN'  
export const SAVE_CURRENT_ZEN = 'SAVE_CURRENT_ZEN'

// ------------------------------------
// Actions
// ------------------------------------
export function requestZen () {  
  return {
    type: REQUEST_ZEN
  }
}

let availableid = 0  
export function receiveZen (value: string) {  
  return {
    type: RECEIVE_ZEN,
    payload: {
      value,
      id: availableid++
    }
  }
}

export function saveCurrentZen () {  
  return {
    type: SAVE_CURRENT_ZEN
  }
}

Here we defined our synchronous actions. Next up, we'll define the asynchronous request to the Github API.

src/routes/Zen/modules/zen.js (continued)
/*  This is a thunk, meaning it is a function that immediately
    returns a function for lazy evaluation. It is incredibly useful for
    creating async actions, especially when combined with redux-thunk! */

export const fetchZen = () => {  
  return (dispatch) => {
    dispatch(requestZen())

    return fetch('https://api.github.com/zen')
      .then(data => data.text())
      .then(text => dispatch(receiveZen(text)))
  }
}

Here we are using the functionality of redux-thunk. Instead of an Object, fetchZen returns a function that returns a Promise.
We can call the function like any other in our components. It is notable that fetchZen itself calls dispatch two times! Before the launch of a new request, we dispatch the synchronous requestZen action, and when that request completes, we dispatch the receiveZen action with the text we just received from the API.

src/routes/Zen/modules/zen.js (continued)
export const actions = {  
  requestZen,
  receiveZen,
  fetchZen,
  saveCurrentZen
}

Finally, when all of our actions have been defined, we export them.

Next, let's take a look at the action handlers and the reducers.

src/routes/Zen/modules/zen.js (continued)
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {  
  [REQUEST_ZEN]: (state) => {
    return ({
      ...state,
      fetching: true
    })
  },
  [RECEIVE_ZEN]: (state, action) => {
    return ({
      ...state,
      zens: state.zens.concat(action.payload),
      current: action.payload.id,
      fetching: false
    })
  },
  [SAVE_CURRENT_ZEN]: (state) => {
    return state.current != null ? ({
      ...state,
      saved: state.saved.concat(state.current)
    }) : state
  }
}

Our action handlers are the real workhorse on our play. They modify the state -- well, rather they return a new state based on the previous one -- according to their input and the type of the action.

  • REQUEST_ZEN simply sets the fetching value to true, which tells the application that we have launched a request and are waiting for it to return
  • RECEIVE_ZEN is a bit more complicated, but in essence it
    • Adds the received zen to the zens array (note that the action.payload already has an id and a value field)
    • Sets the current to be the latest fetched zen
    • Sets the fetching flag to be false, implicating that the fetch was successfully completed, and that we may start another whenever we want
  • SAVE_CURRENT_ZEN checks whether or not we have a current, and if we do, it adds that to the list of saved wisdoms

Note how we use the ES6 syntax here. You might want to use Object.assign instead.
For example, if you choose to use Object.assign, then the REQUEST_ZEN handler would become:

return Object.assign({}, state, { fetching: true })  

Finally, let's take a look at the reducer:

src/routes/Zen/modules/zen.js (continued)
export default function zenReducer (state = initialState, action) {  
  const handler = ACTION_HANDLERS[action.type]

  return handler ? handler(state, action) : state
}

Our reducer is a basic wrapper for action handlers, that simply returns the state unmodified if there's no suitable handler for it.

Well, that is it for the modules/zen.js. There is much room for improvement here, so if you're feeling productive, you might want to add a check for duplicates on the SAVE_CURRENT_ZEN, or add a check to our fetchZen function for not launching another request if the previous has not yet finished -- hint: redux-thunk function can take two parameters, dispatch and getState.

But for now, let's move on!

Components

In terms of the required components, we will go the easy way and create only a single Container component, and a single presentational component. We'll call them ./src/routes/Zen/containers/ZenContainer.js and ./src/routes/Zen/components/Zen.js, respectively.

Let's start with the container component.

./src/routes/Zen/containers/ZenContainer.js
import { connect } from 'react-redux'  
import { fetchZen, saveCurrentZen } from '../modules/zen'

/*  This is a container component. Notice it does not contain any JSX,
    nor does it import React. This component is **only** responsible for
    wiring in the actions and state necessary to render a presentational
    component - in this case, the counter:   */

import Zen from '../components/Zen'

/*  Object of action creators (can also be function that returns object).
    Keys will be passed as props to presentational components. Here we are
    implementing our wrapper around increment; the component doesn't care   */

const mapDispatchToProps = {  
  fetchZen,
  saveCurrentZen
}

const mapStateToProps = (state) => ({  
  zen: state.zen.zens.find(zen => zen.id === state.zen.current),
  saved: state.zen.zens.filter(zen => state.zen.saved.indexOf(zen.id) !== -1)
})

/*  Note: mapStateToProps is where you should use `reselect` to create selectors, ie:

    import { createSelector } from 'reselect'
    const counter = (state) => state.counter
    const tripleCount = createSelector(counter, (count) => count * 3)
    const mapStateToProps = (state) => ({
      counter: tripleCount(state)
    })

    Selectors can compute derived data, allowing Redux to store the minimal possible state.
    Selectors are efficient. A selector is not recomputed unless one of its arguments change.
    Selectors are composable. They can be used as input to other selectors.
    https://github.com/reactjs/reselect    */

export default connect(mapStateToProps, mapDispatchToProps)(Zen)  

That's it. We import the previously created action creators fetchZen and saveCurrentZen from the ./modules/zen. Those will be passed as-is to the underlying component.
In addition, we map two props from the application state in the mapStateToProps function that returns an object.

  • zen tries to find the current zen based on the id and it may return undefined
  • saved returns all the zens that have their id present in the state.zen.saved array

Finally, we use the connect function from react-redux to send the props down to our Zen presentational component.
If this seems somehow weird, or if you're unfamiliar with these functions, it may be a good time to read the excellent redux documentation once more.

Presentational component

All the hard work we've done so far is great, but now it's time to show it to the world! Without further rambling, here's our Zen component:

./src/routes/Zen/components/Zen.js
import React from 'react'  
import './Zen.scss'

export const Zen = (props) => (  
  <div>
    <div>
      <h2>
        {props.zen ? props.zen.value : ''}
      </h2>
      <button className='btn btn-default' onClick={props.fetchZen}>
        Fetch a wisdom
      </button>
      {' '}
      <button className='btn btn-default' onClick={props.saveCurrentZen}>
        Save
      </button>
    </div>
    {props.saved.length
      ? <div className='saved-wisdoms'>
        <h3>
          Saved wisdoms
        </h3>
        <ul>
          {props.saved.map(zen =>
            <li key={zen.id}>
              {zen.value}
            </li>
          )}
        </ul>
      </div>
      : null
    }
  </div>
)

Zen.propTypes = {  
  zen: React.PropTypes.object,
  saved: React.PropTypes.array.isRequired,
  fetchZen: React.PropTypes.func.isRequired,
  saveCurrentZen: React.PropTypes.func.isRequired
}

export default Zen  

If you've ever written JSX, there should a little surprise here. Namely, we modify the DOM slightly based on the props (no point in showing the empty "Saved Wisdoms" header if we don't have any wisdoms to show!).

As a cherry on top, we've included a small SCSS style file to make the UI sparkle:

./src/routes/Zen/components/Zen.scss
.savedWisdoms {
  margin-top: 4rem;

  > ul {
    padding: 0;
    list-style: none;
    font-style: italic;
  }
}
Conclusion

There we have it, a fancy wisdom generator thing. I hope that this was a helpful introduction to the react-redux-starter-kit. Hopefully, now you can start implementing your own application with the help of this collection of technologies.

You can find the whole source code at zen-example.

In the end, there are surely a lot of things that we didn't even talk about, like writing tests (important!), using PostCSS like a pro, or implementing a full backend from the provided express.

Preston Bernstein

Read more posts by this author.

Atlanta, GA, USA