« Previous Tutorial Next Tutorial »

Now that we’ve got our Store up and running, it’s time to create some actions that we can use to change the data we’re storing there, and watch those changes be reflected in our app. That’s right, kids, we’re going to make that zero change to a one, and then back to a zero, using nothing but button clicks.

Let’s get to it.

Create a file in /src/actions called progress.js … once again, no JSX extension here because this file will be pure JavaScript with no rendering.

We’re going to create our first action creator functions here. A reminder that actions themselves are just pure JavaScript objects. They always look like this:

{
  type: 'A_UNIQUE_NAME_ALWAYS_IN_CAPS_WITH_UNDERSCORES',
  data: 'some data' // can be a string, array, object, boolean, integer, etc.
}

The “data” attribute is optional, but all actions must always have a type or Redux won’t know what to do with them. Our functions will each generate one action and “dispatch” it to Redux. You could generate more than one action in a function, but as often as possible it’s best to break things down into creators that create a single action. You can always call multiple creators from a separate, larger function.

Let’s build our increment creator first. It doesn’t need any data, just a type. Your file should look like this:

export const incrementProgress = () => {
  return {
    type: 'INCREMENT_PROGRESS',
  };
};

That’s a very simple function … so simple in fact that it can be shortened using some nice ES6 shorthand (you may notice ESLint is complaining because we haven’t shortened it yet). An arrow function that does nothing except return a value can be done entirely on a single line, omitting not only some braces, but the actual “return” command as well. Nuke that code and replace it with this code, which does exactly the same thing:

export const incrementProgress = () => ({ type: 'INCREMENT_PROGRESS' });

Note: you have to wrap the object in parens, otherwise JavaScript thinks you’re opening the function, not setting a return value. Here are a few other examples of ES6 implicit returns:

const returnTrue = () => true;
const sayHi = (firstName, lastName) => `Hi, ${firstName} ${lastName}!`;
const showLength = myArray => myArray.length;

You only need to wrap function arguments in parens if there’s more than one of them, which is why myArray has no parens in the last example. All right, so we’ve got our increment action creator. Pretty simple, huh? You can probably figure out how to do the decrement function without me providing the code. In fact, let’s make that a little test. Try to write it yourself and if you aren’t sure, highlight the following block to see the answer:

export const decrementProgress = () => ({ type: 'DECREMENT_PROGRESS' });

Did you get it? No worries if not, this is a lot of new info I’m throwing at you!

Save the file and that’s it … our action creators are finished. Now we need to do two things. The first is, tell our progress reducer to listen for our actions. The second is wire our buttons up to fire those creators when we click them. Open /src/reducers/progress.js and just below the switch statement on line 6, add the following code:

    case 'INCREMENT_PROGRESS': {
      return state + 1;
    }
    case 'DECREMENT_PROGRESS': {
      // Don't go lower than zero
      return Math.max(state - 1, 0);
    }

Save the file. See what we’re doing here? We’re watching the “type” value of our action for certain strings, and then acting upon them. This is how Redux works. Every time an action of any sort is passed, it runs through all available reducers to see if the type is matched by any, and if so, it acts upon the state.

Now we have a choice to make. We could “connect” our HomePage component using Redux, which will allow us to access our action creators and trigger them with button clicks, but as we pointed out in our last video, it’s good practice to instead create a container element and then pass the functionality down as props, so let’s do that. In /src/components/home create a file called HomePageContainer.jsx and add the following code:

import React from 'react';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { incrementProgress, decrementProgress } from '../../actions/progress';
import HomePage from './HomePage';

export function HomePageContainer(props) {
  return (
    <HomePage
      incrementFunction={}
      decrementFunction={}
    />
  );
}

Notice that we’re importing our action creators from our action file. We can’t just plug these in to the “incrementFunction” and “decrementFunction” props we’re passing to our HomePage component, though, because they won’t have Redux’s dispatcher attached to them, which is the functionality that actually sends the actions to the reducer. So we need to use Redux to bind those action creators to functions we can send as props, which we do by creating another function below our first function, like this:

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    incrementProgressAction: incrementProgress,
    decrementProgressAction: decrementProgress,
  }, dispatch);
}

We need one more line, which connects this component to Redux. Here it is:

export default connect(null, mapDispatchToProps)(HomePageContainer);

This is where the magic happens. Now the “props” argument that our HomePageContainer function takes will include two methods: incrementProgressAction and decrementProgressAction. Oh, by the way, we need that “null” there as a placeholder for mapStateToProps, which we’re not using in this particular component. If you omit it, Redux will think your mapDispatchToProps function is a mapStateToProps function, and then your code will break.

We can now pass these down to our HomePage component like this:

export function HomePageContainer(props) {
  const { incrementProgressAction, decrementProgressAction } = props;
  return (
    <HomePage
      incrementFunction={incrementProgressAction}
      decrementFunction={decrementProgressAction}
    />
  );
}

See how we’re using variable destructuring to pull those methods out of the props argument? That means we don’t have to write props.incrementProgressAction and props.decrementProgressAction when we call our HomePage component. We’re done here, and we’re in the home stretch. Save the file and open /src/components/home/HomePage.jsx. Delete lines 6 through 8. We’re not going to be using our test function anymore. Sorry, Draco.

Now add a props argument to line 6 like this:

export default function HomePage(props) {

And then add a line below line 6 and let’s do some variable destructuring:

  const { decrementFunction, incrementFunction } = props;

And finally, we’re going to go ahead and nuke everything displayed on the page (the two P tags and the button), so that your return block looks like this:

  return (
    <div className="row">
      <div className="col-sm-12 col-md-8">
      </div>
      <Sidebar />
    </div>
  );

And then add two new buttons like this:

  return (
    <div className="row">
      <div className="col-sm-12 col-md-8">
        <Button onClick={incrementFunction}>Increment</Button>  
        <Button onClick={decrementFunction}>Decrement</Button>
      </div>
      <Sidebar />
    </div>
  );

Save that file, and we’re on to the final step. Right now our template is still calling HomePage, not HomePageContainer. That’s no good, so open /src/components/Template.jsx and change line 5 to:

import HomePage from './home/HomePageContainer';

Save that file, hard-restart your server, and refresh your page. Click your increment button a few times. Your number should be going up. Click the decrement button a few times, and it should go back down … but never below zero. Awesome!

If it’s not working, don’t worry – this is a lot of complicated stuff in just two tutorials. Leave a comment, and we’ll get you sorted out. You can also check out the MusicList Github repo and compare your code to what’s there. Make sure to switch to the “develop” branch!

If it is working, congratulations! Next up, we’re going to install a couple of useful tools that will help us track what actions are being fired, and even step back through them … see you in the next tutorial!

« Previous Tutorial Next Tutorial »