Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save markerikson/34f1a37350a82c8a25149011a7a8585c to your computer and use it in GitHub Desktop.
Save markerikson/34f1a37350a82c8a25149011a7a8585c to your computer and use it in GitHub Desktop.
Reactiflux chat: Dispatching actions, using reducers, action creators, and mapDispatchToProps

[10:39 AM] infected mushroom: Is this analogy correct?

Can we say that the store in the Redux app is like a database?
mapStateToProps is used to retrive (read) the information from the database where as
mapDispatchToProps is used to write the information in the same database?
[10:59 AM] infected mushroom: Okay, I am confused now

On official docs it says

Actions are payloads of information that send data from your application to your store

and we know that mapDispatchToProps also do the same thing.

Then what is the difference?

Is it this that in addition to the modification to the store it provides the props to the container?
[11:00 AM] Paisley: mapDispatchToProps doesn't do that
[11:01 AM] Paisley: mapDispatchToProps just injects action creators into your component
[11:01 AM] Paisley: they are still action creators and they are still sending data to the store
[11:02 AM] Paisley: dispatch is the function that dispatches actions to your store. mapDispatchToProps is just giving you one way to access that function easily.
[11:05 AM] infected mushroom: Okay, let me see if I get it correctly.

mapDispatchToProps is used to modify the store and in addition to it return an object to the container which is passed as Props.

Where as
Actions is the information that we need to sent to the store via dispatch() or action creator

Is it right?
[11:06 AM] infected mushroom: Action could be any operation such as read, update, delete.
[11:12 AM] infected mushroom: Is there a place to get a clear understanding on what is a dispatch and how it is been used in react and redux ecosystem?
[11:16 AM] infected mushroom: Right now I am confused or not able to have strong understanding of the fundamental thing that is DISPATCH
Before moving on I need to understand that.
I will watch Dan's video. If I am not able to understand will read the docs.
[11:21 AM] acemarke: @infected mushroom : yes, you can sorta view a Redux store as a client-side database, in a way. Especially if you have put a lot of normalized data into it.
[11:22 AM] acemarke: okay, reading your last question here
[11:22 AM] acemarke: let me see if I can explain
[11:22 AM] acemarke: So, first fundamental principle of Redux: all of your "write logic" is isolated together inside of the Redux store. The rest of the application is not allowed to just make edits to the state.
[11:22 AM] infected mushroom: Not able to understand the dispatch function for starters.
I am just trying to understand it in more database kind of scene where I am thinking the state as my database. and the actions as the operation on the database(state)
[11:23 AM] acemarke: Instead, only that "write logic" is allowed to update the state
[11:23 AM] acemarke: and that "write logic" is made up of what we call "reducer functions". You can have many smaller reducer functions, but those are all combined at the end into one single "root reducer function"
[11:24 AM] acemarke: which is the function you pass to Redux's createStore: const store = createStore(rootReducer)
[11:24 AM] infected mushroom: What is write logic?
[11:24 AM] acemarke: Next, the only way to actually execute that root reducer is to call store.dispatch(action)
[11:24 AM] acemarke: by "write logic", I mean "code that updates data"
[11:24 AM] acemarke: let me backtrack a bit
[11:24 AM] acemarke: are you familiar with Backbone at all?
[11:25 AM] infected mushroom: No, This is my library for the frontend.
[11:25 AM] acemarke: hmm. trying to come up with a good analogy here to explain things
[11:25 AM] infected mushroom: Sorry! IF i am making it hard for you 😦(edited)
[11:26 AM] acemarke: Well, I'm going to use Backbone as an example for point of this illustration
[11:26 AM] acemarke: in Backbone, you declare Model classes: const User = Backbone.Model.extend({}) [11:26 AM] infected mushroom: Okay, If I am not able to understand will interrupt.
[11:26 AM] acemarke: so let's say I've got a User model
[11:26 AM] acemarke: const userModel = new User({firstName : "Fred", lastName : "Jones"})
[11:27 AM] acemarke: in Backbone, I can pass that userModel variable around to any part of the application
[11:27 AM] acemarke: and any part of the code can call userModel.set("firstName", "Joe")
[11:27 AM] acemarke: including UI views
[11:28 AM] acemarke: but that means that if the user's name is changed, it's very hard to find what part of the code actually called userModel.set()
[11:28 AM] acemarke: does that make sense so far?
[11:28 AM] infected mushroom: Yup.
[11:28 AM] acemarke: okay. So, one of the main points of Redux is that you're not supposed to do that
[11:29 AM] acemarke: all the code that shows the UI is supposed to be kept separate, conceptually, from the code that updates the data
[11:29 AM] acemarke: maybe the actual functions are in the same file
[11:29 AM] acemarke: but the code that shows the UI isn't allowed to make changes to the data
[11:30 AM] infected mushroom: So there is a separation of what is displayed and what actually modify your state.?
[11:30 AM] acemarke: yes
[11:31 AM] infected mushroom: So let me get this right you are saying that UI code which is mostly react isn't allowed to do any kind of the CRUD operations on the state that thing is only done by the redux ?
[11:31 AM] acemarke: yes
[11:31 AM] acemarke: well, mostly
[11:31 AM] acemarke: for purposes of this example, yes
[11:31 AM] infected mushroom: Okay, I have got this till now.
[11:31 AM] acemarke: so, let's keep going with the "user name" example
[11:32 AM] acemarke: in Redux, instead of having a "user model" variable by itself, you would declare that the values are part of Redux state tree
[11:32 AM] infected mushroom: Hold on a min
[11:32 AM] acemarke: and you would write a "reducer function" that is responsible for initializing and updating that piece of state
[11:32 AM] infected mushroom: your this statement is kind of confusing

in Redux, instead of having a "user model" variable by itself, you would declare that the values are part of Redux state tree
[11:33 AM] acemarke: Part of the idea of Redux is that instead of having random data variables in different places in your app, you should put as much of it as possible into one place
[11:34 AM] infected mushroom: basically like a centralized Database for your application.?
[11:34 AM] acemarke: yeah
[11:34 AM] acemarke: let me again throw out some Backbone code as an example
[11:34 AM] infected mushroom: Sure.
[11:35 AM] infected mushroom: I understood that state is basically a database for your application.
[11:36 AM] acemarke:

// users.js  
const UsersModel = Backbone.Model.extend({});  
  
const UsersCollection = Backbone.Collection.extend({model : UsersModel});  
  
const usersCollection = new UsersCollection();  
usersCollection.add({id : 1, firstName : "Fred", lastName : "Jones"});  
  
// posts.js  
const PostsModel = Backbone.Model.extend({});  
  
const PostsCollection = Backbone.Collection.extend({model : PostsModel});  
  
const postsCollection = new PostsCollection();  
postsCollection.add({id : 1, user : 1, text : "Had bacon and eggs for breakfast"});  

[11:37 AM] acemarke: and then maybe some other part of the code comes along and does:

// EditPostView.js  
// somewhere in a click handler:  
const post = postsCollection.get(postId);  
post.set("text", "Went shopping");  

[11:38 AM] acemarke: in other words, any part of the code could grab that PostModel or UserModel and change its values directly
[11:39 AM] infected mushroom: I get your point that you are trying to make instead of having scattered code we are trying to make a central database (State) where all the operation are happened with the actions. I get this point but where i am having most difficulty is what is

Dispatch

mapDispatchToProps

What is

Action Creators

And what is the difference between them?
[11:40 AM] acemarke: getting to that :)
[11:40 AM] acemarke: trying to lay the foundation first
[11:41 AM] infected mushroom: Okay, Wont rush. Can you please continue with your analogy please?
[11:41 AM] infected mushroom: I like the way you are explaining things.
[11:41 AM] acemarke: awright, so let's continue with that users/posts example, but switch over to Redux
[11:41 AM] acemarke: With Redux, the rest of the app isn't allowed to just reach in and make changes to those values
[11:41 AM] infected mushroom: Yup
[11:41 AM] infected mushroom: Yeah
[11:42 AM] acemarke: only the "reducer functions" are allowed to make updates
[11:42 AM] acemarke: so let's throw together a couple quick reducers
[11:42 AM] infected mushroom: what is a reducer function ? :/
[11:42 AM] acemarke: heh
[11:42 AM] acemarke: just to check: have you read the Redux docs at all? :)
[11:42 AM] infected mushroom: Sorry for asking such dumb questions
[11:42 AM] acemarke: the docs really do cover a lot of important stuff :)
[11:43 AM] infected mushroom: I was actually following the stephan grider course. And there was frequent mention of the action and action creator. Got really confused.
[11:43 AM] infected mushroom: so asked here.
[11:44 AM] acemarke: awright, we'll tackle things from first principles
[11:44 AM] acemarke: Are you familiar with the Array.reduce() method?
[11:44 AM] infected mushroom: Can you give me some time. Maybe I can come with my own analogy and you can correct if they are right or not.
[11:44 AM] infected mushroom: Yeah, I know array.reduce() method
[11:44 AM] acemarke: okay. "Reducer functions" act the same way as a function you'd pass to someArray.reduce()
[11:45 AM] infected mushroom: ah! Okay.
[11:45 AM] acemarke: for example, [1, 2, 3].reduce( (sumSoFar, currentNumber) => sumSoFar + currentNumber, 0); // 6
[11:45 AM] acemarke: the first argument is the previous returned value, the second argument is the current item
[11:45 AM] infected mushroom: yeah,
[11:45 AM] acemarke: Redux reducers act the exact same way
[11:46 AM] acemarke: the "previous returned argument" is the "current state" value
[11:46 AM] acemarke: and the "current item" argument is the "action"
[11:46 AM] infected mushroom: Okay.
[11:46 AM] acemarke: so let me throw together a couple sample reducers
[11:46 AM] infected mushroom: That would be cool.
[11:48 AM] acemarke:

// usersReducer.js  
  
const initialState = [];  
  
function usersReducer(state = initialState, action) {  
    switch(action.type) {  
        case "ADD_USER" : {  
            const {firstName, lastName} = action;  
  
            const newUser = {firstName, lastName};  
            return state.concat(newUser);  
        }  
        default : return state;  
    }  
}  

[11:49 AM] infected mushroom: I understood the usersReducer.js
[11:50 AM] acemarke:

// postsReducer.js  
const initialState = {};  
  
function postsReducer(state = initialState, action) {  
        switch(action.type) {  
            case "EDIT_POST_TEXT" : {  
                 const {postId, text} = action;  
                 const post = state[postId];  
  
                 const newPost = {...post, text : text};  
  
                 return {  
                       ...state,  
                       [postId] : newPost  
                 };  
            }   
            default : return state;  
        }  
}  

[11:50 AM] acemarke: real apps would have a lot more cases they handle, I'm just trying to illustrate the basic behavior
[11:51 AM] acemarke: so. These reducers are "write logic". They are responsible for updating their pieces of the state. The rest of the code is not allowed to add a new user, or change the text of a post directly.
[11:51 AM] infected mushroom: Yeah, I understood that thing with the switch statement.
[11:51 AM] acemarke: so next, we need to combine these two reducers together
[11:51 AM] infected mushroom: Correct!
[11:51 AM] infected mushroom: That you could use with the combineReducers you mentioned above in your root reducer?
[11:52 AM] acemarke:

import {combineReducers} from "redux";  
import usersReducer from "./usersReducer";  
import postsREducer from "./postsReducer";  
  
const rootReducer = combineReducers({  
    users : usersReducer,  
    posts : postsReducer;  
});  
  
const store = createStore(rootReducer);  
  
const state = store.getState();  
console.log(state);  
// {users : [], posts : {} }  

[11:52 AM] acemarke: so each of those "slice reducer functions" is responsible for initializing and updating a smaller piece of state
[11:52 AM] acemarke: and then we use combineReducers to merge them into a larger parent object
[11:53 AM] infected mushroom: Yeah,Okay
[11:53 AM] acemarke: which is the Redux store's "state tree"
[11:53 AM] infected mushroom: Okay
[11:53 AM] acemarke: so. We've got these two "slice reducers", and now we have a "root reducer"
[11:53 AM] acemarke: but now we need a way to actually run that root reducer function, and make it produce an updated state object
[11:53 AM] acemarke: and the only way to do that is to call store.dispatch(someActionObject)
[11:54 AM] acemarke: like:
[11:54 AM] acemarke:

store.dispatch({type : "ADD_USER", firstName : "Fred", lastName : "Jones"})  

[11:54 AM] infected mushroom: Okay
[11:54 AM] infected mushroom: Wait a minute please
[11:56 AM] infected mushroom: Okay, let me see if I get this correct or not.
If i need to query(read) or say update or delete anything in my database(which is state) I will use the dispatch method
[11:56 AM] infected mushroom: Is that right?
[11:56 AM] acemarke: no. Only for updates
[11:56 AM] infected mushroom: Okay.
[11:57 AM] acemarke: if you want to "query", you call store.getState() and just look up values out of the returned state tree object
[11:57 AM] infected mushroom: For updates I just use store.dispatch. right?
[11:57 AM] acemarke: yeah
[11:58 AM] acemarke: so. An "action" is just a plain object with a type field, like {type : "ADD_USER", firstName : "Fred", lastName : "Jones"}, or {type : "EDIT_POST_TEXT", text : "Went shopping today"}
[11:59 AM] infected mushroom: Okay..

To read or just query : Store.getState()
to update: store.dispatch()
[11:59 AM] infected mushroom: is that right?
[11:59 AM] acemarke: yeah
[11:59 AM] infected mushroom: Okay.
[12:00 PM] acemarke: now, as programmers we generally don't like hard-coding values in our apps
[12:00 PM] acemarke: if a value gets used in a bunch of places, it's usually better to make a constant and refer to that everywhere, or write a function that encapsulates using that value
[12:01 PM] acemarke: so, you could have many places in your app that do dispatch({type : "ADD_USER"}), etc
[12:01 PM] acemarke: but, for several reasons, it's a good idea to encapsulate the process of actually making that action object into a function
[12:01 PM] acemarke: and those functions are called "action creators", because they create action objects
[12:02 PM] acemarke: so here's how we'd do that for these actions
[12:02 PM] infected mushroom: ah! Got it!
[12:03 PM] acemarke:

function addUser(firstName, lastName) {  
    return {  
        type : "ADD_USER",  
        firstName,  
        lastName  
    };  
}  
  
function editPostText(text) {  
    return {  
        type : "EDIT_POST_TEXT",  
        text  
    }  
}  

[12:03 PM] acemarke: so now you might see code doing dispatch(addUser("Fred", "Jones")) and dispatch(editPostText("Went shopping"))
[12:03 PM] infected mushroom: okay. Type is the action and the firstname and the lastname is the payload.. is that correct?
[12:03 PM] acemarke: note that we still aren't talking about React components yet
[12:03 PM] acemarke: yep
[12:04 PM] infected mushroom: Okay. Only Redux I suppose?
[12:04 PM] acemarke: and as a side note: you will often see people have a key in their actions called "payload"
[12:04 PM] acemarke: this is a common community way of formatting the action objects
[12:04 PM] acemarke: not required, but common
[12:04 PM] acemarke: like, say: {type : "ADD_USER", payload : {firstName, lastName} }
[12:04 PM] infected mushroom: okay.
[12:05 PM] acemarke: So, all an "action creator" is is a function that encapsulates literally putting together an action object
[12:05 PM] infected mushroom: Okay!
[12:05 PM] acemarke: so, now we get to the React side of things
[12:06 PM] acemarke: the React-Redux library and its connect() function create wrapper components that abstract out the process of interacting with the store
[12:06 PM] acemarke: ie, you could write that code yourself, but you'd have to duplicate it every time you want a component to use Redux
[12:06 PM] acemarke: (skimming over some details here because you don't need to worry about them yet)
[12:07 PM] acemarke: So, let's say we have a <UserComponent>
[12:07 PM] acemarke: and we're going to connect it to Redux
[12:07 PM] infected mushroom: connect() basically connect my React and Redux together.. More like Connector between the application and the Database. Is that right way?
[12:07 PM] acemarke: roughly, yeah
[12:09 PM] acemarke:

const mapStateToProps = (state, ownProps) => {  
    const user = state.users[ownProps.index};  
  
    return {user};  
}  
  
const UserComponent = (props) => (  
    <div>  
        User: {props.user.firstName + " " + props.user.lastName}  
    </div>  
);  
  
const ConnectedUserComponent = connect(mapStateToProps)(UserComponent);  

[12:09 PM] acemarke: ConnectedUserComponent is the "wrapper component" generated by connect()
[12:10 PM] acemarke: it will call the mapStateToProps function you provide, take the data you extracted from Redux, and pass that data as props to the component
[12:11 PM] infected mushroom: Okay. I knew that the mapStateToProps basically extract the whole state and pass it to the container as the props.
[12:11 PM] acemarke: sort of. Your mapState function is given the entire state, and it should extract whatever specific values this component needs
[12:11 PM] acemarke: so that's the state part
[12:11 PM] acemarke: now for the dispatching
[12:11 PM] infected mushroom: Okay!
[12:12 PM] acemarke: by default, connect will also give your component the store's dispatch function as a prop
[12:12 PM] acemarke: let me rework that UserComponent example
[12:14 PM] acemarke:

class UserComponent extends React.Component {  
  
    onChangeUserNameClicked = () => {  
        this.props.dispatch({type : "CHANGE_USER_NAME", firstName : "Joe", lastName : "Cool"});  
    }  
  
    render() {  
        const {user} = this.props;  
  
        return (  
            <div>  
                User: {user.firstName + " " + user.lastName}  
                <button onClick={this.onChangeUserNameClicked }>Change User Name</button>;  
           </div>  
        );  
    }  
}  

(edited)
[12:14 PM] acemarke: so connect passes in this.props.dispatch, and the component can dispatch actions
[12:15 PM] acemarke: good so far?
[12:16 PM] infected mushroom: wait a min please
[12:21 PM] infected mushroom: Okay.
[12:21 PM] infected mushroom: Got it!
[12:21 PM] acemarke: awright, so, final couple steps(edited)
[12:21 PM] acemarke: we can use action creators here too:
[12:21 PM] acemarke: let me change the function name to be clearer
[12:22 PM] acemarke: edited the example to be onChangeUserNameClicked
[12:22 PM] acemarke: so now let's use an action creator instead
[12:22 PM] acemarke:

onChangeUserNameClicked  = () => {  
    this.props.dispatch(changeUserName("Joe", "Cool"));  
}  

[12:22 PM] acemarke: same thing, but using an action creator
[12:22 PM] acemarke: right?
[12:23 PM] infected mushroom: Yup. Got it
[12:23 PM] acemarke: okay. But, by referring to dispatch, our component "knows" that it's talking to Redux
[12:23 PM] acemarke: maybe we want to reuse this React component in another app that doesn't use Redux
[12:24 PM] acemarke: we'd rather be able to do this.props.changeUserName("Joe", "Cool")
[12:24 PM] infected mushroom: Yeah, You are right. But one thing. Doesn't mapStateToProps also means that our app is talking to redux?
[12:24 PM] acemarke: maybe in this app that should dispatch a Redux action, but in another app, it should update state in a parent component
[12:24 PM] acemarke: yeah, but the component is receiving that data as this.props.user
[12:24 PM] acemarke: ie, it doesn't "know" that value is coming from Redux
[12:25 PM] acemarke: it's just coming from "a parent component"
[12:25 PM] acemarke: so, connect() takes a second argument: a function you provide called mapDispatchToProps
[12:25 PM] acemarke: if you provide that function, it passes in the reference to dispatch
[12:26 PM] infected mushroom: Got it
[12:26 PM] acemarke: and you can do stuff like this:
[12:26 PM] acemarke:

function mapDispatchToProps(dispatch) => {  
    return {  
        changeUserName : (firstName, lastName) => dispatch({type : "CHANGE_USER_NAME", firstName, lastName})  
    }  
}  

[12:27 PM] acemarke: and now the component can call this.props.changeUserName("Joe", "Cool"), and that function will call dispatch()
[12:27 PM] infected mushroom: Within dispatch you are calling the action right?
[12:27 PM] acemarke: "dispatching" the action
[12:28 PM] acemarke: calling the dispatch function, and passing in the action object as the parameter
[12:28 PM] infected mushroom: Correct
[12:28 PM] acemarke: but, we've already established that using action creator functions is a good practice
[12:28 PM] acemarke: so we could do:
[12:28 PM] infected mushroom: Yes
[12:28 PM] acemarke:

function mapDispatchToProps(dispatch) => {  
    return {  
        changeUserName : (firstName, lastName) => dispatch(changeUserName(firstName, lastName))  
    }  
}  

[12:29 PM] infected mushroom: Yeah, Isn't using the action creator a better practice ?
[12:29 PM] acemarke: and Redux provides a utility function called bindActionCreators to simplify that "wrapping up functions with dispatch" approach:
[12:29 PM] infected mushroom: okay
[12:29 PM] acemarke:

import {bindActionCreators} from "redux";  
  
function mapDispatchToProps(dispatch) => {  
    return {  
        changeUserName : bindActionCreators(changeUserName, dispatch)  
    }  
}  

[12:30 PM] acemarke: and we can make it even simpler. bindActionCreators can take an object of action creators
[12:30 PM] acemarke:

const actionCreators = {changeUserName};  
  
function mapDispatchToProps(dispatch) {  
    return bindActionCreators(actionCreators, dispatch);  
}  

[12:31 PM] acemarke: and finally, you can actually pass that object of action creators right to connect, instead of writing a real function named mapDispatchToProps
[12:31 PM] acemarke:

const actionCreators = {changeUserName};  
  
const ConnectedComponent = connect(mapStateToProps, actionCreators)(MyComponent);  

[12:31 PM] acemarke: and the component still gets this.props.changeUserName
[12:31 PM] acemarke: and as soon as you call it, it will dispatch the action to the store
[12:32 PM] acemarke: so, recapping:
[12:32 PM] acemarke: - an "action" is a plain object with a type field. The only way to make Redux update the state is to call store.dispatch(someAction)
[12:32 PM] acemarke: - an "action creator" is just a function that creates and returns an action object
[12:32 PM] acemarke: - mapDispatchToProps lets you wrap up dispatching actions so that the component doesn't "know" it's talking to Redux
[12:33 PM] acemarke: - connect() lets you pass an object full of action creators instead, so you don't actually need to write a real mapDispatchToProps function
[12:33 PM] infected mushroom: Crystal Clear everything! Man! YOu should teach few course on the udemy!
[12:33 PM] acemarke: :)
[12:33 PM] infected mushroom: thank you acemarke!
[12:34 PM] acemarke: and NOW you might want to read my post on "Why use action creators?"
[12:34 PM] acemarke: http://blog.isquaredsoftware.com/2016/10/idiomatic-redux-why-use-action-creators/
Mark's Dev Blog
Idiomatic Redux: Why use action creators? · Mark's Dev Blog
[12:34 PM] infected mushroom: just came to know you are a maintainer of the redux! You should be thinking how dumb these questions are which i asked!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment