Understanding a Simple React, React-Router and Redux Application in ES2015. Part 2

This is the third article in a series of posts on react, redux and webpack. Others:

  1. configuring webpack
  2. Understanding a simple react, redux and react-router app in es2015, part1.

Here, we will cover the routes configuration, the actions and all the react components starting from the Root.

Root

If you remember from the last post, the Root component is called inside of index.js, the entry point to our app. It receives redux’s store and the browser history as props.

...
<Root store={store} history={history} />
...

If you open app/containers/Root.js you will see the same as configureStore, namely, a conditional to see what environment you are in. Let’s check out the dev version:

import React, { Component, PropTypes } from 'react';
import { Provider } from 'react-redux';
import DevTools from './DevTools';
import { Router } from 'react-router';
import routes from '../routes';

export default class Root extends Component {
    render() {
        const { store, history } = this.props;
        return (
            <Provider store={store}>
                <div>
                    <Router history={history} routes={routes} />
                    <DevTools />
                </div>
            </Provider>
        );
    }
}

Root.propTypes = {
    store: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired
};

Lot’s of imports here:

  • from react we import the Component and PropTypes objects. The Component object is what we extend when creating a react component. The PropTypes object is used to reinforce types in our props, i.e. passing a string when an array was expected will throw a warning in the console. We also need the react lib itself, removing it will give an error.
  • The provider from react-redux is a utility higher-order component to make the store available to all components inside who wish to subscribe to it.
  • Devtools is the monitor we see when we run the app, docked on the right. It shows us the current and past states.
  • Router from react-router. This is the component where we will configure our app’s routes.
  • Routes are our app’s route. We will check them out later.

All our Root component does is wrap our app around the Provider component. We render the Router inside of it, along with the DevTools (if we are in a dev env).

Router

Open up /app/routes.js. This is where you distribute your app into routes. It’s also the place where you decide what component goes with what route.

import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/App';
import FilterableTable from './containers/FilterableTable';
import About from './components/About';

export default (
	<Route path="/" component={App}>
		<IndexRoute component={FilterableTable} />
		<Route path="/about" component={About} />
	</Route>
);

Our app is pretty simple: our root path belongs to the App component. Then we have two routes inside of our root: and index and “/about”. This means whatever route we are in, “/” or “/about”, our App component will always render.

React will only render FilterableTable if we are in the root path “/”. The same logic applies to About.

App

Go to /app/components/App.js

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

const App = ({ children }) =>
    <div>
        <h1>Filter table</h1>
        { children }
        <footer>
            <Link to="/">Filterable Table</Link>
            <Link to="/about">About</Link>
        </footer>
    </div>;

App.PropTypes = {
    children: PropTypes.object
}

export default App;

If you’re asking yourself why our components are divided into the /components and /containers directories. Then have a look at this part of the redux docs. Basically, the components directory holds the “dumb” components, those that are not aware of redux and its store. They only receive props and care about presentation. The components inside /containers are aware of the app’s state as they are connected to the redux store.

This App component is available everywhere so it’s smart to put things like site navigation in here. That’s why we put a footer with the two routes we have. You must also pass in the children prop, which references the components that must be rendered inside the App for a given route.

If you navigated to “/about”, the children component will actually be the About component. React-Router, for good or bad, abstracts all of this. That’s why we must take care when we define our routes.

FilterableTable

Open up /app/containers/FilterableTable.js

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { filterTable } from '../actions';
import ProductTable from '../components/ProductTable';

const FilterableTable = ({ filter, onFilter }) => {
    let input;

    return (
        <div className="filterable-table">
            <input
                value={filter}
                ref={node => {input = node;}}
                onChange={() => onFilter(input.value)} />

            <ProductTable filter={filter} />
        </div>
    );
};

FilterableTable.propTypes = {
    filter: PropTypes.string,
    onFilter: PropTypes.func
};

const mapStateToProps = (state) => {
    return {
        filter: state.filter
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        onFilter: filterText => dispatch(filterTable(filterText))
    };
};

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

We import connect from react-redux. This function connects or subscribes a component to the store, making it aware of any changes to the state.

We also import filterTable from our actions. This function is really simple:

// app/actions/index.js

export function filterTable(filter) {
    return {
        type: types.FILTER,
        filter
    };
}

It takes the current filter and return and action object with the action type and that filter. This action object gets sent to the reducer so the reducer can compute the next state.

We define a component called FilterableTable. It has an input where you can filter the ProductTable. Basically, whenever there’s a change in the filter, the state changes, so the ProductTable component reacts to that and changes accordingly.

It receives 2 props, filter and onFilter. Where are this props coming from? They are certainly not from the app component, nor from the router. They come from the connect function of react-redux. This function acts as a wrapper. Check out the last lines of FilterableTable. We are not exporting the FilterableTable component directly, we are actually exporting a wrapper of that component.

The wrapper takes 2 params when being created: mapStateToProps, and mapDispatchToProps.

With these 2 params, you can map which part of the state you want to make available to the component (FilterableTable), as well as which actions.

When we call connect(mapStateToProps, mapDispatchToProps) we are configuring the wrapper component, and we pass in what part of the state we want to know about, and which actions we want present so that we can dispatch them to the store.

When we do  connect(mapStateToProps,mapDispatchToProps)(FilterableTable);We are doing the above mentioned, configuring the wrapper component but also creating it by passing in the FilterableTable component. Internally, its probably dong something like this:

const connect (mapStateToProps, mapDispatchToProps) => {
    const filter = doSomethingThatGetsState(mapStateToProps);
    const onFilter = doSomethingThatGetsActions(mapDispatchToProps);

    return FilterableTable => class extends Component {
        render () {
            return (
                <FilterableTable
                    filter={filter}
                    onFilter={onFilter} />
            )
        }
    }
};

In short: when dealing with containers, we export a wrapper function (i.e. the container) and pass in the component that should receive the state and callbacks it needs.

Note: Normally you would have the FilterableTable component in another file, under the components directory. You would only need to have the mappings and the connect in this file (basically the wrapper). I just like to have it on the same file to better understand what’s gong on, specially if it’s not too big.

ProductTable

/app/components/ProductTable.js

import React, { PropTypes } from 'react';
import ProductRow from './ProductRow';

const products = [
  { category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football' },
  { category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball' },
  { category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball' },
  { category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch' },
  { category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5' },
  { category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7' }
];

const ProductTable = ({ filter }) => {
    let rows = [];

    products.forEach((p) => {
        const nameLC = p.name.toLowerCase();
        const filterLC = filter.toLowerCase();

        if (nameLC.indexOf(filterLC) !== -1) {
            rows.push(
                <ProductRow key={p.name} data={p} />
            );
        }
    });

    return <div> {rows} </div>;
};

ProductTable.PropTypes = {
    filter: PropTypes.string
};

export default ProductTable;

This component’s only concern is to show the ProductRows that should be visible under the current filter. It does this by comparing the filter with every product name, using javascript’s indexOf function. It only pushes the matching ProductRows to the array that will later be used to show the products.

Note: the products array doesn’t really belong here, it should be in a database, but you already knew this.

ProductRow

import React, { PropTypes } from 'react';

const ProductRow = ({ data }) =>
    <div>
        <p>{data.name} = {data.price} </p>
    </div>;

ProductRow.PropTypes = {
    data: PropTypes.object
};

export default ProductRow;

Easy stuff, this component’s only concern is to show the product row: name and price.

 

 


Comments

5 responses to “Understanding a Simple React, React-Router and Redux Application in ES2015. Part 2”

  1. Hi man!
    Thanks for that nice article.
    I’m new guy in React, so please explain – where is React.createClass()
    Why don’t you use it?
    How to implement componentDidMount()?
    Thanks!

    1. Jean-Pierre Sierens Avatar
      Jean-Pierre Sierens

      Hello
      You’re welcome!

      React.createClass is a method for when you are using the old way of creating react components. In ES6+ (the new javascript) you can create react components the following way:

      ‘class someComponent extends React.Component {}’
      The above creates a class that extends the Component class from the React lib, this is where you define your component and methods like render() or componentDidMount(). You can find an example of this on the Root.dev.js file.

      There is another way to create a react component. It’s what I mostly use in this tutorial: stateless components. stateless components are simplified react components, it’s when you don’t need to define any lifecycle methods, where you don’t define any state and also where you don’t need any context (this). Basically stateless components are dumb components who only care about presentation and executing callbacks when an event occurs. An example is the ProductRow component defined above in the article:

      const ProductRow = ({ data }) =>

      {data.name} = {data.price}
      ;

      It only cares about presenting the name and the price of the product, so we can just define the component as a function.

      Here’s an article on how to do React with es6+
      https://babeljs.io/blog/2015/06/07/react-on-es6-plus

      Here’s a section on stateless components:
      https://facebook.github.io/react/docs/reusable-components.html#stateless-functions

  2. why are all the files .js instead of .jsx?

    1. Because it is not required and in the webpack.config.js the loader’s test is /.js?%/ which means js or js(and something else here) so jsx, jsy,jsa would all match, actually. The loader for that test is babel.

      I use webstorm and it yells at me for using jsx in a js file but it can detect that and asks me ‘do you want to check this file for jsx’ essentially to which I say yes. I would imagine in other editors there are ways to let it parse jsx in js files. Suffice it to say you can rename all the js files that contain jsx to .jsx and everything *should* work exactly the same.

      I do realize your message is 7 months old but perhaps newer people reading this article can benefit :)

  3. Cowboy_X Avatar
    Cowboy_X

    Typo: “It takes the current filter and return and action object…”

    should read “It takes the current filter and returns an action object…”

    I spent about five minutes trying to figure out what that says…

Leave a Reply

Your email address will not be published. Required fields are marked *