« Previous Tutorial Next Tutorial »

Now that we have actions and reducers working, wouldn’t it be nice if we could see what they were doing using some sort of development tool or tools? Well, I have exciting news for one and all: the React community has provided us with some sort of development tool or tools! Lots of them, in fact. We’re going to get two up and running today. This should be a quick one, so let’s get started.

First we install a few modules we’ll need. Head for your command prompt or terminal window, and in your musiclist directory type the following:

yarn add --dev redux-logger redux-devtools-log-monitor redux-devtools-dock-monitor

Let that run, and then switch over to Sublime Text. First, we’re going to create a DevTools component. This will be a handy overlay (which can be easily hidden) that will show us step by step what’s happening to our app state, and allow us to do things like step back through the state, or even cancel a single state change in the middle of things. It’s really useful for seeing exactly how your data’s being mutated. In /src/components/shared, create a file called DevTools.jsx and add the following code:

import React from 'react';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
import { createDevTools } from 'redux-devtools';

const DevTools = createDevTools(
  <DockMonitor
    toggleVisibilityKey="ctrl-h"
    changePositionKey="ctrl-q"
    defaultIsVisible
  >
    <LogMonitor theme="tomorrow" />
  </DockMonitor>,
);

export default DevTools;

We’re using two pre-made components here. LogMonitor contains the state monitoring functionality, and DockMonitor gives us a handy place to display data, allowing us to move it around our window, hide it, and so forth. Save this file, and we’re ready to move on.

Open /src/store/index.js. We’re going to make a bunch of changes, here, including laying the groundwork for hot-reloading when we make changes to our reducers (sweet!). There will be more work in a future tutorial to get that going, but we might as well make the change to this particular file while we’re editing it.

We have quite a few changes to make here, but don’t worry, they’re not complicated. First, change line 1 by adding “applyMiddleware” and “compose” to the list of imports, like this:

import { applyMiddleware, combineReducers, compose, createStore } from 'redux';

Below that, add a new line, which will pull in our logger – a handy utility that logs every action to the console, and also shows app state before and after the action is applied. I actually find this one more useful, in a lot of ways, than the DevTools, but there’s no reason not to work with both. Here’s the code:

import { logger } from 'redux-logger';

After that, we need to tie our DevTools component in with our Store, so we need to import it. Above line 4, our ProgressReducer import, add the following code:

import DevTools from '../components/shared/DevTools';

Our combineReducers block stays the same, because we aren’t adding any new reducers right now. It’s still just the progress reducer. Soon we’re going to have more of these, but that’ll be another tutorial. Below that block, on line 11, we need a new block that looks like this:

const enhancer = compose(
  applyMiddleware(logger),
  DevTools.instrument(),
);

The compose function allows us to chain any middleware we want to use before running our output through the DevTools component. Middleware in React is much like middleware in express. It’s stuff you want to happen in between calling a function, and receiving its final response. In this case, we’re attaching our logger and our DevTools to everything Redux is doing under the hood. Next up, we need to delete the other two lines in the file. These ones:

const Store = createStore(combinedReducers);

export default Store;

We’re going to replace those with a function, which we’ll export and call elsewhere. Here it is:

export default function configureStore(initialState) {
  const store = createStore(combinedReducers, initialState, enhancer);

  // Hot reload reducers
  if (module.hot) {
    module.hot.accept('../reducers/progress', () =>
      store.replaceReducer(ProgressReducer),
    );
  }

  return store;
}

Obviously, that hot-reloading bit has some issues ... we’re not going to want to hot reload only our progress reducer. We’ll be fixing that eventually, but let’s keep this short and move forward. We’re exporting a function that creates our Store, now, instead of our Store object itself. So we’re going to need to call that function somewhere.

Here's what the whole file should look like:

import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import { logger } from 'redux-logger';

import DevTools from '../components/shared/DevTools';
import ProgressReducer from '../reducers/progress';

const combinedReducers = combineReducers({
  progress: ProgressReducer,
});

const enhancer = compose(
  applyMiddleware(logger),
  DevTools.instrument(),
);

export default function configureStore(initialState) {
  const store = createStore(combinedReducers, initialState, enhancer);

  // Hot reload reducers
  if (module.hot) {
    module.hot.accept('../reducers/progress', () =>
      store.replaceReducer(ProgressReducer),
    );
  }

  return store;
}

Save that file, then open up /src/index.jsx and let’s make some changes. Find this line:

import Store from './store';

and change it to the following:

import configureStore from './store';

Then, just above that line, add the following:

import DevTools from './components/shared/DevTools';

In between your “import templateContainer” and “const renderApp” lines, add this one:

const Store = configureStore();

And now we just need to add our DevTools to our output. Right under this line:

        <Component />

Add the following:

        <DevTools />

Ah, but wait … Provider expects only a single child element. Why that is, I honestly don’t know, but it’s easy to solve. That child element can contain as many elements as we want, so all we have to do is wrap Component and DevTools with a simple div tag, like this:

        <div>
          <Component />
          <DevTools />
        </div>

There we go. Were all set here. This is what your whole index.jsx file should look like at this point (I added a bit more spacing up at the top for clarity):

// Default export from a module
import React from 'react';

// Individual exports from a module
import { AppContainer } from 'react-hot-loader';
import { Provider } from 'react-redux';
import { render } from 'react-dom';

// CSS from a module
import 'bootstrap/dist/css/bootstrap.css';

// CSS from a local file
import './css/musiclist.scss';

// Default export from a local file
import DevTools from './components/shared/DevTools';
import configureStore from './store';

// JSX Modules
import TemplateContainer from './components/TemplateContainer';

const Store = configureStore();

const renderApp = (Component) => {
  render(
    <AppContainer>
      <Provider store={Store}>
        <div>
          <Component />
          <DevTools />
        </div>
      </Provider>
    </AppContainer>,
    document.querySelector('#react-app'),
  );
};

renderApp(TemplateContainer);

if (module && module.hot) {
  module.hot.accept('./components/TemplateContainer', () => {
    renderApp(TemplateContainer);
  });
}

Go ahead and hard-reset your server, then hard-refresh your browser. Check that out, a handy overlay. Make sure to open your JavaScript console, too. Now, if you click your increment decrement buttons, you can see the actions being broadcast to the console, and stacking up in your overlay. Now the really cool stuff: click on one of the actions in the overlay, and watch as it’s temporarily removed from your app’s history (you can click the “sweep” button to nuke it forever). Note that certain buttons, like “commit” and “revert” permanently impact your app state, but don’t broadcast actions to your console. This is because the Dev Tools are wired right into your Store, so you didn’t have to write any actions to make them work.

You can play around with the tools here to get a sense of how they work. Note that ctrl-h will hide/show the overlay. You can also check out the &DevTools documentation if you want to deep dive into what you can do with these tools.

That’s about it for this tutorial. Next up, we’re going to do some cleanup, including laying the first groundwork for swapping between development and production environments. See you soon!

« Previous Tutorial Next Tutorial »