mapStateToProps, and why you may not need mapDispatchToProps when you start Redux
When I started React+Redux, one of the first things I did not understand were the obscure mapDispatchToProps and mapStateToProps functions. Although the documentation is clear for experimented Redux users, I will try to explain a bit differently and write what I wish someone told me when I began.
TL;DR: you can skip the very beginning
The very beginning: Higher-Order Components
Redux uses the notion of higher-order component (nickname HOC). It is often described as a React pattern, a wonderful way to refactor code, etc. That’s true. However, the documentation (https://reactjs.org/docs/higher-order-components.html) is definitively obscure for a beginner, especially if you have an Object Oriented background. So, I will summarize here what you need to know to start:
A Higher Order Component is just a way to enhance normal components. How does it work ? It adds props to your components !
Let’s see a basic example: you have two components, DisplayHello and DisplayQuestion. They both need to access the same info, the nickname of our user. The basic way to do that is to propagate props from the root of your app (which maintains a state) as presented below:
App.js:
import React, {Component} from 'react';
import DisplayHello from "./DisplayHello";
import DisplayQuestion from "./DisplayQuestion";
class App extends Component {
state = {
username: 'ovrsea',
};
render() {
return (
<div>
<DisplayHello username={this.state.username}/>
<DisplayQuestion username={this.state.username}/>
</div>
);
}
}
export default App;
DisplayHello.js:
import React, {Component} from 'react';
class DisplayHello extends Component {
render() {
return (
<p>
Hello {this.props.username} !
</p>
);
}
}
export default DisplayHello;
DisplayQuestion.js:
import React, {Component} from 'react';
class DisplayQuestion extends Component {
render() {
return (
<div>
What do you want to order today, {this.props.username} ?
</div>
);
}
}
export default DisplayQuestion;
However, it could become heavy to handle if you have a lot of information and/or components. It is not the role of the root to handle your app data ! This is where HOC are useful. We create a component which “pushes” the info to the components which need them ! How does it work ? It is just a function that takes a component as input and returns the same component with new props. The code of this magic trick is below :
import React, { Component } from 'react'
export default function ourFirstStore(WrappedComponent) {
return class extends Component {
state = {
username: 'ovrsea',
};
render() {
return <WrappedComponent username={this.state.username} {...this.props} />;
}
};
}
DisplayHello updated:
import React, {Component} from 'react';
import ourFirstStore from "./ourFirstStore";
class DisplayHello extends Component {
render() {
return (
<p>
Hello {this.props.username} !
</p>
);
}
}
export default ourFirstStore(DisplayHello);
DisplayQuestion updated:
import React, {Component} from 'react';
import ourFirstStore from "./ourFirstStore";
class DisplayQuestion extends Component {
render() {
return (
<div>
What do you want to order today, {this.props.username} ?
</div>
);
}
}
export default ourFirstStore(DisplayQuestion);
And App.js:
import React, {Component} from 'react';
import DisplayHello from "./DisplayHello";
import DisplayQuestion from "./DisplayQuestion";
class App extends Component {
render() {
return (
<div>
<DisplayHello/>
<DisplayQuestion/>
</div>
);
}
}
export default App;
You can notice that all the data is now in the wrapper and not in App.js anymore. We just created a very basic data store accessible anywhere in your app !
Redux uses (almost) the same idea to give you the possibility to access the data, but also to modify your store. And as we have seen, this data and methods are accessible through props. This is why we have to… mapStateToProps and mapDispatchToProps !
mapStateToProps, the easy one: access your data
If you understood the previous example, you see how easy it is to access data from a store. The problem is: your store might be huge, and your component does not need to be aware of all the changes in it. You want to choose which variables are accessed by which component. This is where mapStateToProps comes into action !
Basically, this function tells your component what props will be added. You need state.username ? Then add it! The email ? Same. The “map” itself is just an object returned by your function.
If our state looks like this:
{
username: ‘initial name’
}
then, our component will be:
import React, {Component} from 'react';
import {connect} from "react-redux";
class DisplayQuestion extends Component {
render() {
return (
<div>
What do you want to order today, {this.props.nameAsProps} ?
</div>
);
}
}
function mapStateToProps(state){
return {
nameAsProps: state.username,
}
}
export default connect(mapStateToProps)(DisplayQuestion);
Easy, isn’t it ?
mapDispatchToProps, the tricky one. That you don’t need at first.
The only way to update data in your store is to dispatch actions. Well, something only a few know: dispatch is accessible as a props if you don’t provide a mapDispatchToProps function.
So, my 2 cents here if you are a true React + Redux beginner: don’t use mapDispatchToProps ! You certainly don’t need to understand the concept of middleware, nor what an action creator is, for now. Those are powerful patterns, of course. But I wish someone told me: “don’t worry, you will understand later”.
So here is a simple app: you have an initial name, let’s say ‘initial name’, and when you click on a button, it changes the name, to, let’s say ‘new name’. Then, our component can look like this:
ChangeNameButton.js:
import React, {Component} from 'react';
import {connect} from "react-redux";
const action = {
type: 'MODIFY_USERNAME',
payload: 'new name',
};
class ChangeNameButton extends Component {
render() {
return (
<input
type="button"
value="Change my name !"
onClick={() => this.props.dispatch(action)}
/>
);
}
}
export default connect()(ChangeNameButton);
and our store (+ reducer here, but it’s better if you have separate files) :
store.js:
import { createStore } from 'redux'
function modify_username(state = {username: 'initial name'}, action) {
switch (action.type) {
case 'MODIFY_USERNAME':
return {username: action.payload}
default:
return state
}
}
export default createStore(modify_username);
Let’s try ! On the right, you can see the store state and the dispatched actions.
Going further: using mapDispatchToProps
The goal of this article was to show a very basic architecture to understand the concept of connected components, store, dispatch, etc. If you use dispatch directly as a props, you will soon see that you cannot really reuse your actions. It will be also more difficult to pass parameters. So, once you are familiar with those concepts, feel free to use mapDispatchToProps :) !
Happy coding !