DEV Community

Dustin Myers
Dustin Myers

Posted on • Updated on

What Even Is A Dispatch Function?

Learning redux? Or useReducer? Then chances are you've been frustrated by the black magic that is the dispatch function 🧙‍♂️! Well, luckily you have found this post. I will help you understand what happens under-the-hood and remove the mystery behind dispatch.

https://images.unsplash.com/photo-1551269901-5c5e14c25df7?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb

What makes dispatch so hard?

There are a couple of reasons that learning Redux or the reducer hooks can be confusing.

First - both flows rely on a programming paradigm called "functional programming". Thinking in this way requires you to switch your mental model of how to manage data in your application. For more on functional programming, read the first half of this article - Functional Programming in JavaScript: How and Why. The basic idea here that makes these patterns hard is that data is flowing from function to function, and often changes shape a little (or even a lot 😬) before your state is updated and the component re-renders.

Second - both flows "abstract" some of the logic into different functions. Think about it. If you're using Redux, you call an action creator function and POOF... a reducer is called and state and an action object are passed in. Like, what the heck?!? 😡 The reducer hook flow has one less layer of abstraction, but there is still some there that adds to the confusion.

Rebuilding dispatch

I think looking at how the dispatch function is built really helps remove the mystery behind reducers. So let's build a simple implementation of dispatch to see the logic that is abstracted out of our view. We start with the function definition.

function dispatch() {

}
Enter fullscreen mode Exit fullscreen mode

Wooooo 🎉! We're doing great so far 😁. Next step, we will add action as a parameter for the function.

function dispatch(action) {

}
Enter fullscreen mode Exit fullscreen mode

So, with this, we know that when the dispatch function is called, it will be passed an action object as an argument. If you're using useReducer or useDispatch, you already know this. Upon some kind of event occurring in the UI, you, not the Redux library, call the dispatch function like this: dispatch({ type: 'ACTION_TYPE' }). If you're using Redux and the connect function, even that part is abstracted away from your view and it’s the Redux library that calls the dispatch function. We’ll talk more on that towards the end. Let's continue though.

Now we need to do a couple of checks. We need to make sure that the action object that is passed in is an object, and that it has a type property. If either of those is not true, we will throw an error. When a reducer function is written, it assumes that those are both true.

function dispatch(action) {
  // check that the action argument is an object
  if (typeof action !== 'object' || obj === null) {
    throw new Error('actions must be plain object.');
  }

  // check that the action object has a 'type' property
  if (typeof action.type === 'undefined') {
    throw new Error('Actions may not have an undefined "type" property.'
  }
}
Enter fullscreen mode Exit fullscreen mode

Good. Now we can build our reducers with confidence knowing that any action that is dispatched will be an object and will have a "type" property.

Now the exciting part! The next thing we will do is call the reducer from within the dispatch function. This is the abstraction part that hides from our view of what is happening behind-the-scenes. There are a couple of points that we need to cover before we can write this though.

The dispatch function is in the same scope as the current state of the app. So that means that inside the dispatch function, we have access to an object called currentState that is the current state in our app.

In that same scope is the reducer function that we have written and passed into createStore or useReducer. So the dispatch function also has access to reducer - our reducer function (no matter what we called it) that we passed in. That means that the dispatch function can invoke the reducer function.

Here’s a very simplified version of what that looks like:

const createStore = () => { 
  // 😮 yep, it’s createStore! But that’s for another article… 

  // state will be initialized then stored here
  const currentState = {};

  // your reducer, or combined reducers, will be accessible here
  const reducer = null;

  // dispatch function in the same scope will have access to the most current state and your reducer(s)
  const dispatch = (action) => {
    // … all the codes
  }
Enter fullscreen mode Exit fullscreen mode

🤯 I know, I know… really cool to see what it looks like under-the-hood, right? Functions and objects. Welcome to functional programming in JavaScript! Once you see it written out like this, it starts to come together! But there is still just a little more to explore.

Let's think about everything that we've learned so far and combine that new knowledge with what we know about reducers.

  • dispatch has access to currentState and reducer.
  • When dispatch is called, it receives an action object as an argument.
  • A reducer function, when invoked, is passed two arguments - state (meaning the current state) and action. See where I'm going with this?

Inside dispatch we will now call reducer and pass in currentState and the action object.

function dispatch(action) {
  // check that the action argument is an object
  if (typeof action !== 'object' || obj === null) {
    throw new Error('actions must be plain object.');
  }

  // check that the action object has a 'type' property
  if (typeof action.type === 'undefined') {
    throw new Error('Actions may not have an undefined "type" property.');
  }

  // call the reducer and pass in currentState and action
  // reducer and currentState are within scope, action is the parameter passed into the function
  reducer(currentState, action);
}
Enter fullscreen mode Exit fullscreen mode

Look at that closely... when an action is dispatched, or in other words, when we invoke dispatch and pass in an action object, the dispatch function calls our reducer and passes in the current state and the action object! 🤩 It's all starting to make sense!

Well, there's one last part to this - updating the state. Think about how you write a reducer function. What does it return? It returns a new state object, right? You've followed immutable principles to return a copy of the old state, updated with new data based on whichever action you had dispatched. So when the dispatch function does this - reducer(currentState, action); - that function call is going to return a brand new state object. Our dispatch function here needs to update currentState with the new state object that is returned by calling the reducer.

function dispatch(action) {
  // check that the action argument is an object
  if (typeof action !== 'object' || obj === null) {
    throw new Error('actions must be plain object.');
  }

  // check that the action object has a 'type' property
  if (typeof action.type === 'undefined') {
    throw new Error('Actions may not have an undefined "type" property.');
  }

  // call the reducer and pass in currentState and action
  // capture the new state object in currentState, thus updating the state
  currentState = reducer(currentState, action);
}
Enter fullscreen mode Exit fullscreen mode

And voila! We have built a simple implementation of the dispatch function. Now, of course, there is more to this in the actual implementations. In Redux, dispatch needs to tell the app that the state has been updated. This happens through listeners and subscriptions. In the useReducer hook, React recognizes that the state was updated and re-renders the component. The updated state is then returned to the component from where the useReducer hook was called.

Regardless of the extra implementations, building out the dispatch function here will really help us understand what is happening under-the-hood when we call dispatch from our components.

Redux and action creators

If you're using Redux and connect, there is one more layer of abstraction to explore. With the connect function, you pass action creators into an object in the connect function. The action creators are then passed to the component via props. In your component, when you call the action creator, it will call dispatch for you. That's the added layer of abstraction. Let's look at what connect does under-the-hood (again in a simplified version).

// inside the connect function implementation
dispatch(actionCreator());
Enter fullscreen mode Exit fullscreen mode

So, connect wraps the dispatch function around the action creator call. When the action creator is invoked, it returns an action. So the above evaluates down to:

dispatch({ type: 'ACTION_TYPE' });
Enter fullscreen mode Exit fullscreen mode

which we now understand will call the reducer! Wooo! 🚀

Conclusion

Hopefully, this helps remove the black magic of reducers and dispatch! If you think through the logic flow, you will realize that this is all about functions calling functions and passing data around. And now that the black magic of Redux has been removed a bit, you can get back to the fun part of building web apps with React and Redux ⚛️!

Latest comments (18)

Collapse
 
calebtripp profile image
Caleb Tripp

Thank you. I'm brand new to React and this is helping me grok dispatch and reducers.

Collapse
 
sodj profile image
Sodj • Edited

I came here trying to understand why the actionCreator function can't call the dispatch directly ... why do we have to do dispatch(actionCreator()); instead of just actionCreator();

Collapse
 
zallex profile image
Zalyotov Alexey

Thanks a lot! It's pretty clear and helpful

Collapse
 
dustinmyers profile image
Dustin Myers

Thank you! I'm really glad this helped!

Collapse
 
keitharnold profile image
KeithArnold

Thank you Dustin, I've been spinning my wheels for more than a week to understand useReducer and at last it makes sense!

Collapse
 
dustinmyers profile image
Dustin Myers

Seriously the best kind of reply! Thank you, and I'm glad it was helpful!

Collapse
 
habibaman766 profile image
Habib Ur Rehman

The disptach function calls a reducer function, but i came to know that the control flows in this way.
dispatch -> store -> combineReducer -> reducer
I am not understanding that disptach function is calling the exact reducer function but why the the data is passed to store first then to combineReducer and then to reducer.\
Please can you explain this

Collapse
 
kelviniot profile image
KelvinIOT • Edited

Hello Habib,

  • So i understand that Store holds the whole state tree of your application.
  • Then you pass to the store, your combineReducer which combines all your separate reducers in an object.
  • When the dispatch function calls the store, it actually tries all your Reducers.
  • But the Reducers listen for events checking for a matching action-type, so if the action-type passed to the dispatch finds a match in a reducer, that particular reducer runs.
Collapse
 
dustinmyers profile image
Dustin Myers

This is absolutely right! Sorry I was late on the reply. Hopefully kelvin's response helped you understand how this system works with combined reducers. Feel free to reach out if you have any other questions! And thank you Kelvin. That was a great explanation 👍👍

Collapse
 
habibaman766 profile image
Habib Ur Rehman

Thank you very much for understanding.

Collapse
 
gaddopur profile image
gaddopur

Thank you very much. It helped very much. Before reading this I was in confusion but you clear all of my doubts once again thank you very much.

Collapse
 
dustinmyers profile image
Dustin Myers

Awesome!! So glad it helped 😊

Collapse
 
ashutoshjs profile image
AshutoshJs

it helped :)

Collapse
 
dustinmyers profile image
Dustin Myers

Yay!! That makes me so happy!

Collapse
 
johnkeers profile image
John Keers • Edited

If you have multiple reducers, can you tell me how the dispatch function knows which reducer to call?
Is there a switch statement internally with action type as the parameter?

Update: Looking at the source code for createStore it looks like it only ever takes a single reducer? Does that mean you simply use combineReducer() and createStore will choose the correct reducer to use?

Collapse
 
dustinmyers profile image
Dustin Myers • Edited

Actually when you dispatch an action, it will go through all your reducers that you've "combined". It's the action type that's the magic here. You probably only have one reducer that handles that specific action type. All the other reducers will look at the action type, and return their slice of the state untouched.

Collapse
 
rarrrleslie profile image
Leslie Rodriguez

I tried, but I could not get away from Redux. So, thanks for this amazingly simple refresher! :)

Collapse
 
dustinmyers profile image
Dustin Myers

Thanks Leslie! 😁