Action Creators

One thing we haven't talked much about yet is how we make things happen in Redux. Sure, we covered .dispatch() but that's quite basic. We just showed how to directly dispatch an action by doing: store.dispatch({type: 'SOME_ACTION'})

But, frequently we'll want to dispatch something as a result of a user's click or other action. Ideally, we just want a function to call, right? Let's say, for example, we're building an online store, and there are a handful of places in the UI that show products and give you the option of adding them to your cart. Let's assume that ultimately, when clicked it should always result in dispatching the same action.

It may start to get a bit arduous to have to type this each time:

store.dispatch({ type: 'ITEM_ADDED_TO_CART', payload: 47 })

If you're really paying attention, you'll also remember that we said actions, ideally, should report things that happened, not make things happen. If the dispatch is the report, then what actually sends the report!?

Plus, for something like adding an item to a cart, this probably involves an API call of some kind, and then potentially updating a bunch of UI based on what happened. At this point, it makes no sense to repeat that logic every place we need to do the same thing.

What we need instead, is a function, perhaps called doAddToCart(ID) that initiates the actual API call. Then, as a side effect triggers actions that report how the request is progressing to the store.

That's the idea behind an "action creator."

Not everything is complicated; sometimes the only thing the action creator needs to do is to dispatch a single action to the store.

Here's an example of a really simple action creator:

function doAddToDoItem(text) {
  return { type: 'TODO_ADDED', payload: text }
}

Or... as I like to write them because I'm likely the world's laziest typist:

const doAddToDoItem = payload => ({ type: 'TODO_ADDED', payload })

It's just a plain old function that returns an action object. With this approach, we can repeat ourselves a bit less, and now we can dispatch like so:

store.dispatch(doAddToDoItem('be awesome today'))

Using an action creator like this is nice because now each time we want to add a todo, we don't have to remember the action constant to use or how the payload should be structured. Plus, if we refactor down the road, this thin layer of abstraction means we have fewer changes to make.

Also, if down the road we decide that adding a todo should trigger an asynchronous fetch, the code that is calling the action creator doesn't need to change. Using action creators allows us to expresses the intent without having to worry about the implementation; this includes caring about whether it is async or not. So even if it has to make 50 different API calls that will re-position satellites in orbit before finishing, the intent has not changed. That's the power of abstraction.

Can we take this a step further?

Wouldn't it be kind of nice if we could just call a function called doAddToDoItem() without having to do the store.dispatch() part? Arguably the store.dispatch() part is another unnecessary coupling. Well, as it turns out JavaScript makes it very easy to make a function that will do that.

If in our code we have store as a variable, and a given action creator function, like doAddToDoItem from above, it'd be quite simple to write a function that, when called, would do the dispatch() part, right?

// assume we've created a store here
const store = createStore(someReducer)

// and we have our plain action creator function from above
const doAddToDoItem = payload => ({ type: 'TODO_ADDED', payload })

// we'd simply need to make a new function that did both things:
const boundActionCreator = text => store.dispatch(doAddToDoItem(text))

As it turns out, this is a common pattern in Redux. We frequently have action creators that we want to bind to the store. It may sound a bit complicated, but it is just creating a new "wrapper" function that does the store.dispatch() part.

Since this is something we frequently need to do when using Redux, the library includes a function for this called: bindActionCreators().

It's just a utility that takes an action creator (or a whole object of action creators) and binds them all to the dispatch function you got from a store.

import { bindActionCreators, createStore } from 'redux'
// assume we've defined and combined our reducers
// in another file somewhere.
import rootReducer from './reducers/root'

// we've got a store!
const store = createStore(rootReducer)

// our action creator
const doAddToDoItem = payload => ({ type: 'TODO_ADDED', payload })

// we can do it with a single item like this and it will return
// as single "bound" action creator
const boundAddToDoItem = bindActionCreators(doAddToDoItem, store.dispatch)

// or we can use it to bind a whole object of them at once:

// let's define a few more action creators
const doRemoveToDoItem = id => ({ type: 'TODO_REMOVED', payload: id })
const doClearToDos = () => ({ type: 'TODOS_CLEARED' })

const boundToDoActions = bindActionCreators(
  {
    add: doAddToDoItem,
    remove: doRemoveToDoItem,
    clear: doClearToDos
  },
  store.dispatch
)

// now we have an object with all the same keys
// where each key is now a pre-bound function
// we can just call directly. And it will dispatch
// the necessary action.
boundToDoActions.add('see the world')
boundToDoActions.remove('23324')
boundToDoActions.clear()

A super common pitfall

I see a lot of folks who are learning Redux make this mistake, so I want to draw attention to it. I'll use a JSX component here for our example, but it could be anything that renders a view and registers an event handler.

They try to do something along these lines:

// import an action creator
import { doClearToDos } from './all/my/action-creators'

// a simple component with a button
// we can click to clear all ToDo items:
export const (props) => (
  <button onClick={doClearToDos}>Clear 'em all!</button>
)

The example above may look reasonable, but can you spot the error?

Remember, our action creator has no awareness our Redux store whatsoever! It's nothing but a plain function that returns a plain JavaScript object! Last time I checked, returning an object from a simple click handler that you attach to a DOM element doesn't do anything!

So please, just remember that simply calling an action creator does nothing for your application state. Only dispatching actions can update your application state.

So you have to store.dispatch(doClearToDos()) or you have to have access to an action creator that has been successfully bound to the store already. We'll delve a bit deeper into how this works in practice in the chapter on binding state to views.

There you go, you now know what action creators are!

Chapter recap

  1. An action creator is merely a function that returns an action object.
  2. Redux includes a utility function called bindActionCreators for binding one or more action creators to the store's dispatch() function.
  3. Calling an action creator does nothing but return an object, so you have to either bind it to the store beforehand, or dispatch the result of calling your action creator.

results matching ""

    No results matching ""