Skip to content
Chris edited this page Mar 24, 2016 · 2 revisions

Counter App

This is a short write up of a Demo application. This application will have two routes:

  • /: The main route to increase and decrease a counter
  • /stats: View how often the decrease/increase button has been clicked

Setup

Create a fresh project:

mkdir counter-demo && cd counter-demo
yo react-webpack-redux
npm install --save react-router

Further we will need two actions, one for increasing the counter, and one for decreasing it, so lets add them:

yo react-webpack-redux:action increase
yo react-webpack-redux:action decrease

Let's also create a new container for the stats:

yo react-webpack-redux:container Stats

We will also need components to display the counter UI and the stats:

yo react-webpack-redux:component counter
yo react-webpack-redux:component stats

The last thing we need is a reducer to keep track of our counter and the clicks:

yo react-webpack-redux:reducer counter

Lets start the app to see our changes live from now on:

npm start

Setting up the reducer

Our reducer will handle both actions, the increase and the decrease. Further it will keep track of the current counter state and will keep track of how often the increase and decrease buttons have been pressed. Edit src/reducers/counter.js:

import {INCREASE, DECREASE} from './../actions/const'

const initialState = {
  count: 0,
  increase: 0,
  decrease: 0
};

module.exports = function(state = initialState, action) {
  let nextState = Object.assign({}, state);

  switch(action.type) {
    case INCREASE: {
      nextState.count++;
      nextState.increase++;

      return nextState;
    } break;

    case DECREASE: {
      nextState.count--;
      nextState.decrease++;

      return nextState;
    } break;

    default: {
      return state;
    }
  }
}

Counter implementation

First of all we prepare our counter component, it consists of a button to increase and decrease the counter and a place to show the current count in src/components/CounterComponent.js, lets add that component to our main component so we can see the changes live. Edit src/components/Main.js to render our counter component:

require('normalize.css');
require('styles/App.css');

import CounterComponent from './CounterComponent';

import React from 'react';

let yeomanImage = require('../images/yeoman.png');

class AppComponent extends React.Component {
  render() {
    return (
      <div className="index">
        <img src={yeomanImage} alt="Yeoman Generator" />
        <CounterComponent
          actions={this.props.actions}
          count={this.props.counter.count}
        />
      </div>
    );
  }
}

AppComponent.defaultProps = {};

export default AppComponent;

Next step is to add the UI to our counter component, edit src/components/CounterComponent.js:

'use strict';

import React from 'react';
import { Link } from 'react-router';

require('styles//Counter.css');

class CounterComponent extends React.Component {
  render() {
    return (
      <div className='countainer'>
        <div className='counter-wrapper'>
          <button onClick={this.props.actions.decrease}>-</button>
          <span>{this.props.count}</span>
          <button onClick={this.props.actions.increase}>+</button>
        </div>
        <hr />
        <div className='link-wrapper'>
          <Link to={'/stats'}>Show stats</Link>
        </div>
      </div>
    );
  }
}

CounterComponent.displayName = 'CounterComponent';

export default CounterComponent;

At this point the counter is in a working state, surf to the page and you can increase and decrease the counter.

Stats implementation

Lets get the Stats Component ready, src/components/StatsComponent.js

'use strict';

import React from 'react';

require('styles//Stats.css');

class StatsComponent extends React.Component {
  render() {
    return (
      <div className="stats-wrapper">
        <div>Decreased: {this.props.counter.decrease}</div>
        <div>Increased: {this.props.counter.increase}</div>
      </div>
    );
  }
}

StatsComponent.displayName = 'StatsComponent';

export default StatsComponent;

Next step is to set up the stats container, src/containers/Stats.js

import React, {
  Component,
  PropTypes
} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import StatsComponent from '../components/StatsComponent';

class Stats extends Component {
  render() {
    const {counter} = this.props;
    return <StatsComponent counter={counter}/>;
  }
}

Stats.propTypes = {
  actions: PropTypes.object.isRequired,
  counter: PropTypes.object.isRequired
};

function mapStateToProps(state) {
  const props = { counter: state.counter };
  return props;
}

function mapDispatchToProps(dispatch) {
  const actions = {};
  const actionMap = { actions: bindActionCreators(actions, dispatch) };
  return actionMap;
}

export default connect(mapStateToProps, mapDispatchToProps)(Stats);

Routing

The last thing left to do is set up the routing, we will do this in index.js:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './stores';
import App from './containers/App';
import Stats from './containers/Stats';
import { Router, Route, browserHistory } from 'react-router';

const store = configureStore();
render(
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path="/" component={App} />
      <Route path="/stats" component={Stats} />
    </Router>
  </Provider>,
  document.getElementById('app')
);

And that's it, you can increase and decrease the counter and click the link to see the stats.

Hints

Technically the routing could also take place in the App container and we would not need the Stats container, but since react-router does not support hot reloading, warnings and errors will happen if placed anywhere else than index.js