Getting started with Redux using the Mullet Stack

Intro and Installation

This article will offer a refresher on Redux and instructions on how to use it with the Mullet Stack. To understand everything here, you’ll need to be comfortable with React, and you should at least have read through the official Redux docs. Hopefully, this tutorial will provide a good starting point for integrating Redux into your full-stack Node projects.

For starters, let’s grab the mullet framework (https://mullet.io/). We’ll be using the development version, so follow the instructions on the webpage or just use the following commands:

git clone https://github.com/lynnaloo/mullet
cd mullet
npm i

Mullet is a great project for learning Node.js created by Linda Nichols and friends. It uses Walmart’s Hapi server framework on the backend and React on the frontend.

Let’s also install the Redux packages we’ll need:

npm install --save redux react-redux redux-logger redux-thunk

To illustrate Redux’s usefulness, we’ll make an async call from one component, and use the response data to update state in another component. The Star Wars API is an enormously useful resource that allows us to retrieve data on fictional characters, so we’ll use that for the async call.

Restructure

Before we come to Redux, let’s restructure the project a bit. Wrap src/components/Main.js in a component and export it, instead of rendering directly:

import React, { Component} from 'react';
import Facebook from './Facebook';

export default class Main extends Component {
  render() {
    return (
      <Facebook
        title="Welcome to the Mullet Stack."
        subtitle="Facebook in the front. Walmart in the back."
      />
    );
  }
}

Now let’s import that into a new file src.index.js:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Main from './components/Main';

class Index extends Component {
  render() {
    return (
      <Main />
    );
  }
}

ReactDOM.render(
  <Index />,
  document.getElementById('facebook')
);

Remember to update webpack’s entrypoint (webpack.config.js):

module.exports = {
  entry: ['./src/index.js'],

So now if we run…

npm run webpack
npm start

…we should see basically the same thing as before, except this time everything’s wrapped in an Index component. Why did we do this? Adding a level to the hierarchy allows us to componentize our app more easily. For instance, in addition to Main, we could have top-level Header and Footer components. In Redux — as we’ll talk about — these three components could be “smart” components, passing mainData, headData, and footerData from Redux into their “dumb” child components.

Star Wars API

So what do we want our app to do? We said we’d use the Star Wars API to make an async call. Let’s hook up a button that returns data on a random starship. According to the docs, we just need to hit this endpoint: swapi.co/api/starships, get the length of “results”, and then randomly send one to the UI. In fact, let’s create a couple buttons — one for starships, one for characters, and one for planets.

So first let’s create a Buttons component that will be a child of the Facebook component.

import React, { Component } from 'react';

export default class Buttons extends Component {
  render() {
    return (
      <div>
        <div onClick={() => console.log('selected starships')}>Starships</div>
        <div onClick={() => console.log('selected people')}>People</div>
        <div onClick={() => console.log('selected planets')}>Planets</div>
      </div>
    );
  }
}

And add it to the Facebook component (I’m also cleaning out some of the default Mullet markup):

import React, { Component } from 'react';
import Buttons from './Buttons';

const Styles = {
  flexContainer: {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    height: '100%'
  },
  header: {
    textAlign: 'center'
  },
};

export default class Facebook extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div style={Styles.flexContainer}>
        <header style={Styles.header}>
          <Buttons />
        </header>
      </div>
    );
  }
}

Now npm run webpack and npm start again, and we should have three divs that log simple confirmations when you click them.

Thinking about actions

Where does this tie into Redux? Well, we want our buttons to do more than just log something to the console. In the past, we may have used jQuery with AJAX to make a request to the API, but it gets tricky when you have several interacting components at different levels of the component hierarchy, asynchronously updating shared data. This is where Redux comes in.

Redux has two elements: actions and reducers. Let’s talk about actions first.

An action is any event that changes UI state. So, if we had a dropdown menu that opens when the user clicks it, the menu’s onClick would fire an action.

It gets a little more complicated with async actions. In our case there will be 2 actions: one, requestData, will be fired immediately on a button’s onClick event. However, at some point in the future this request will get a response — receiveData —which will be a separate action.

Let’s create an actions file, src/actions.js:

import fetch from 'isomorphic-fetch';

export const REQUEST_DATA = 'REQUEST_DATA';
export const RECEIVE_DATA = 'RECEIVE_DATA';

export function requestData(category) {
  return {
    type: REQUEST_DATA,
    category
  };
}

export function receiveData(category, data) {
  return {
    type: RECEIVE_DATA,
    category,
    data
  };
}

function selectRandomly(results) {
  return results[Math.floor(Math.random() * results.length)];
}

export function fetchData(category) {
  return function(dispatch) {
    dispatch(requestData(category));
    return fetch(`https://swapi.co/api/${category}`)
      .then(response => response.json())
      .then(json => selectRandomly(json.results))
      .then(selected => dispatch(receiveData(category, selected)));
  };
}

Things to note: the first line imports fetch, which is similar to XMLHttpRequest. We then define our action types. Then we have requestData and receiveData, which are our action creators. They are the functions that actually determine how UI state is updated. If we call requestData("starships"), our UI will be made aware that this request is being made. When we receiveData("starships", {...}), our UI will know that the request is finished, and it will have the returned data.

fetchData is the function we actually call inside the Buttons component. It allows us to execute an async action by wrapping the 2 synchronous action creators in what’s called a “thunk”. We won’t get into Redux theory here, but the idea is that this action returns a function, which will get handled by the redux-thunk middleware we installed earlier.

Setting up the reducers

Let’s set actions aside for a moment and set up our reducers. In src/reducers.js:

import { combineReducers } from 'redux';
import { REQUEST_DATA, RECEIVE_DATA } from './actions';

function mainData(state = {}, action) {
  switch(action.type) {
    case REQUEST_DATA:
      return Object.assign({}, state, {
        loading: true
      });
    case RECEIVE_DATA:
      return Object.assign({}, state, {
        loading: false,
        category: action.category,
        data: action.data
      });
    default:
      return state;
  }
}

const reducers = {
  mainData
};

export default combineReducers(reducers);

When an action is dispatched, the reducer takes the current state and that action, and returns an updated state. So, our reducer is a function mainData that takes state and action as params and returns a new state (get familiar with the Object.assign syntax, it gets used a lot in Redux).

Using Redux inside React

We need to do one more thing before using Redux inside our React components: we have to create a store. The store will allow the reducers to be updated by actions, and allows us to access the reducer state from React. It also allows us to apply helpful middleware such as redux-thunk and redux-logger. Create src/configureStore.js:

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import mainData from './reducers';

const loggerMiddleware = createLogger();

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware
)(createStore);

const store = createStoreWithMiddleware(mainData);

export default store;
`

So, we’re creating a wrapper around the basic createStore function that applies thunkMiddleware and loggerMiddleware, then we’re using that wrapper to create a store with the reducers’ mainData.

Now comes the good part. To use Redux in the React app, we’ll wrap the whole app in a special component exported by react-redux called Provider. This component gives React access to the store. Update src/index.js to look like this:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import store from './configureStore';
import Main from './components/Main';

class Index extends Component {
  render() {
    return (
      <Provider store={store}>
        <Main />
      </Provider>
    );
  }
}

ReactDOM.render(
  <Index />,
  document.getElementById('facebook')
);

Smart and dumb components

Now that we’ve wrapped the React UI in a Provider, we officially have a Redux app. However, we still need to pull reducer data into components individually. Before jumping in, we should remember that it’s best to have a few “smart components”, top-level components that connect directly to the Redux store, and many “dumb components”, children of the smart components that receive Redux data as props from their parents. Main will be a smart component (if we were to set up top-level Header and Footer components, and they needed state, they’d also be “smart components”).

In src/components/Main.js:

import React, { Component} from 'react';
import { connect } from 'react-redux';
import Facebook from './Facebook';
import { fetchData } from '../actions';

function mapStateToProps(state) {
  return {
    mainData: state.mainData
  };
}

class Main extends Component {
  render() {
    return (
      <Facebook
        title="Welcome to the Mullet Stack."
        subtitle="Facebook in the front. Walmart in the back."
        fetchData={(cat) => this.props.dispatch(fetchData(cat))}
      />
    );
  }
}

Main.propTypes = {
  dispatch: React.PropTypes.func
};

export default connect(mapStateToProps)(Main);

By wrapping the component in the connect function exported by react-redux, we make dispatch available as a prop. We also make the reducers available through state.{reducerName}, and we use the function mapStateToProps to pass individual reducers into props. We’ll come back to this, but first let’s hook up actions.

As you can see, we’re passing a new prop down to Facebook. fetchData is a function that takes a parameter, “cat”, and dispatches the imported action fetchData. Let’s see how to pass it through src/components/Facebook.js:

...
      <div style={Styles.flexContainer}>
        <header style={Styles.header}>
          <Buttons fetchData={this.props.fetchData} />
        </header>
      </div>
...

Really nothing fancy here. Now let’s check out src/components/Buttons.js:

import React, { Component } from 'react';

export default class Buttons extends Component {
  render() {
    return (
      <div>
        <div onClick={() => this.props.fetchData('starships')}>Starships</div>
        <div onClick={() => this.props.fetchData('people')}>People</div>
        <div onClick={() => this.props.fetchData('planets')}>Planets</div>
      </div>
    );
  }
}

Right, so now instead of just logging something to the console, clicking on one of these buttons is going to immediately fire requestData, and then fire receiveData when the Star Wars API responds.

Let’s take a look: npm run webpack, npm start.

The Redux Logger

…is really, really useful. Remember the middleware we hooked up? It complicated things a bit, but now, if you press one of the buttons, you’ll see a detailed log of the Previous State, Action, and Next State for ever action you use. This is at the core of developing production Redux apps, so get good at reading this output.

Rendering the data

So, hopefully what you see in the console after receiveData fires is an object with a key, data, containing data on a random starship, character, or planet (depending on the category from the button).

We need to go back up into src/components/Main.js to grab this from mainData and pass it down. But first let’s create a component to display the data, src/components/Display:

import React, { Component} from 'react';

export default class Display extends Component {
  switchOnCategory(cat) {
    switch(cat) {
      case 'starships':
        return (
          <div>
            <div>Name: {this.props.displayData.name}</div>
            <div>Model: {this.props.displayData.model}</div>
            <div>Class: {this.props.displayData.starship_class}</div>
          </div>
        );
      case 'people':
        return (
          <div>
            <div>Name: {this.props.displayData.name}</div>
            <div>Gender: {this.props.displayData.gender}</div>
          </div>
        );
      case 'planets':
        return (
          <div>
            <div>Name: {this.props.displayData.name}</div>
            <div>Climate: {this.props.displayData.climate}</div>
            <div>Terrain: {this.props.displayData.terrain}</div>
          </div>
        );
      default:
        return null;
    }
  }

  render() {
    return (
      <div>
        {this.switchOnCategory(this.props.category)}
      </div>
    );
  }
}

Display.propTypes = {
  displayData: React.PropTypes.object,
  category: React.PropTypes.string
};

So we render slightly different markup based on what type of data we’re displaying.

Now let’s go back to src/components/Main, add this component, and pass data into it:

import React, { Component} from 'react';
import { connect } from 'react-redux';
import Facebook from './Facebook';
import Display from './Display';
import { fetchData } from '../actions';

function mapStateToProps(state) {
  return {
    mainData: state.mainData
  };
}

class Main extends Component {
  render() {
    return (
      <div>
        <Facebook
          title="Welcome to the Mullet Stack."
          subtitle="Facebook in the front. Walmart in the back."
          fetchData={(cat) => this.props.dispatch(fetchData(cat))}
        />
        <Display
          category={this.props.mainData.category}
          displayData={this.props.mainData.data}
        />
      </div>
    );
  }
}

Main.propTypes = {
  dispatch: React.PropTypes.func,
  mainData: React.PropTypes.object
};

export default connect(mapStateToProps)(Main);

One thing to notice here is that we have to wrap the two components in <div> tags now. This is because render() needs to return a single element.

Summary

From here it should be easy to expand the app to do (actually) useful things. Redux is tricky at first, but don’t give up! Once you get familiar with the patterns it uses, it’s an extremely useful tool for efficiently and cleanly building complex UIs.

Here are some suggestions for projects to expand on this app:

  • Use CSS-Modules or Styled Components. These options scale much better than the simple inline styles used in the basic mullet framework.
  • Set up Hot Reloading, a must for production development
  • Add Header and Footer components with their own dataflows

Full code for what we’ve done so far can be found here: https://github.com/weyj4/mullet-redux

Previous

Microservices Series #2: The state of enterprise software (analytical evidence)

Teaching clients to adopt new technologies — even the ones they don’t understand

Next