Redux’s Mysterious Connect Function

tyler clark
MoFed
Published in
7 min readJan 19, 2017
This post is mainly for beginners to React/Redux. However I’ve found even experienced developers struggle with the third and fourth connect() parameters. So read on anyways!

So you’ve created your first action creators, reducers, and store files… Now what? How does the state from your reducers and the functions from your action creators get used in your application? And how do you pass the necessary data to your action creators to update the store? A quick google search on this matter will show many others with the same question(s)…

How do you bridge data coming from your React code to your Redux code and vise versa?

Enter Redux’s Container

Let’s start by defining some terminology here. As mentioned above, there needs to be glue that connects the Redux data flow with the React components. The container is meant to be the file that holds all the data and functions required to connect store state and action creators actions to React component props. It is in this file where your action creators are imported, the Redux ‘connect’ function is imported, the parent React component is imported, and any other methods/data are imported (i.e routing and initial server loading).

import {connect} from 'react-redux';import Template from '../components/Template.js'import {increment, decrement} from '../actions/sidebar.js';import {isBrowser} from '../env.js'; 

First off, it is important to note that not all React/Redux applications call the highest parent file a ‘container’. I’ve seen through various Redux trainings that not all developers refer to this file/component as the container. However, it is in fact referred to as the container within Redux’s documentation.

Now the connect function. The documentation for connect within Redux is difficult to understand, which creates a sense of mystery. Simply think of this as a component that takes props as parameters, which in return supplies actions and state to the provided React component via props… Confused yet? Well let’s break this down some more.

The following shows the connect function breakdown:

connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component);

As you can see from above, there are four parameters within the connect function, with another argument that accepts the imported React component.

  1. mapStateToProps
  2. mapDispatchToProps
  3. mergeProps — optional
  4. options — optional

We’ll break down what each one of these do in a moment. It is important to note that when connect renders successfully, you will be able to see that connect is now the parent component of the provided React component within the container file.

mapStateToProps

You will see shortly that some of these parameters can be a function or an object. However, mapStateToProps needs to be a function. This function does exactly what the name suggests, connect state from the store to corresponding props. This make it possible to access your reducer state objects from within your React components. This function will subscribe to the Redux store and any updates will update props automatically. mapStateToProps needs to return an object, where the key is the new prop name to be used in the React app and the value is the name of the reducer function.

// ES5function mapStateToProps({ reducer1: reducer1, reducer2: reducer2}) {
return ({reducer1 : reducer1, reducer2 : reducer2 });
}
// ES6const mapStateToProps = ({ reducer1, reducer2}) =>
({reducer1, reducer2 });

You might ask.. Why didn’t you mention needing to import these reducers into your container file, like the action creators? Well connect subscribes to the store automatically and these state objects (or reducers) will be provided automatically.

mapDispatchToProps

mapDispatchToProps can either be a function or an object. Now assuming that you have some knowledge of Redux, you’ll understand the importance of using ‘dispatch’. If not, here is a basic definition of ‘dispatch’ from Redux:

Dispatches an action. This is the only way to trigger a state change.

The store’s reducing function will be called with the current getState() result and the given action synchronously. Its return value will be considered the next state.

So essentially the only way to update your store/state is by using ‘dispatch’ with an action creator.

import {actionCreator1, actionCreator2} from '../actions/main.js';
const bindActionsToDispatch = dispatch =>
(
{
actionCreator1 : () => dispatch(actionCreator1()),
actionCreator2 : (e) => dispatch(actionCreator2(e))
}
);

There are two important things happening in the code above. The first is we’re are setting up props that hold our action creators. I like to keep the names of the props the same as the name of the action creator. I’ve found that by keeping the state props shown earlier and the action creator props the same name as the function/object, it helps debugging later.

The second thing is binding the action creators to dispatch. Without this binding firing an action creator will do nothing. Now mapDispatchToProps is not the only way of bind dispatch and action creators, there is another way called bindActionCreators.

import { bindActionCreators } from 'redux';
import * as TodoActionCreators from './TodoActionCreators';
let boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)

This is the least common way of binding as stated from Redux:

The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.

mergeProps

I personally struggled to understand mergeProps. First of all, it is an optional parameter and is a function. The documentation on this particular function does not explain much as to it’s purpose and use cases. Hence adding to the mystery of the connect function.

In the React/Redux applications I have worked on, I have not needed to use mergeProps. If it is used, this function is passed the result of mapStateToProps(), mapDispatchToProps(), and the parent props. With this data available, it is easy to use props inherited from connect’s parent and combine them with an action creator. For example, if you are working with React Router, the variables assigned in the route path are passed as props to the connect function, if it is the connect function’s parent. You could then use those props and pass it along to your action creators to update your store with the needed URL parameters.

const mergeProps = (state, actions, {notebook, note}) => ({
...state,
...actions,
onLoad: notebook && note
? () => {
return Promise.all([
actions.initActiveNotebookAndNote({notebook, note})
])
}
: actions.onLoad
})

The above code is receiving state from mapStateToProps, actions from mapDispatchToProps, and is creating a new prop called ‘onLoad’. This prop ‘onLoad’ is combining or ‘merging’ (as it’s name states) props from it’s parent component (React Router) and an action creator together. That action creator can be used to update data within a Reducer:

export const initActiveNotebookAndNote = ({notebook, note}) => ({
type: SET_CURRENT_NOTE,
notebook,
note
});

options

The options parameter is an object. It is used to customize the behavior of the connect function. The acceptable ‘options’ are the following:

  1. pure- Boolean, if true connect() will avoid re-rendering (will not update)
  2. areStatesEqual- Compares new store state vs. old*
  3. areOwnPropsEqual- Compares new props vs. old*
  4. areStatePropsEqual- Compares new mapStateToProps vs. old*
  5. areMergedPropsEqual- Compares new mergeProps vs. old*

*Only when pure functions

These options are not commonly used and are best explained by Redux:

You may wish to override areStatesEqual if your mapStateToProps function is computationally expensive and is also only concerned with a small slice of your state. For example: areStatesEqual: (prev, next) => prev.entities.todos === next.entities.todos; this would effectively ignore state changes for everything but that slice of state.

You may wish to override areStatesEqual to always return false (areStatesEqual: () => false) if you have impure reducers that mutate your store state. (This would likely impact the other equality checks is well, depending on your mapStateToProps function.)

You may wish to override areOwnPropsEqual as a way to whitelist incoming props. You'd also have to implement mapStateToProps, mapDispatchToProps and mergeProps to also whitelist props. (It may be simpler to achieve this other ways, for example by using recompose's mapProps.)

You may wish to override areStatePropsEqual to use strictEqual if your mapStateToProps uses a memoized selector that will only return a new object if a relevant prop has changed. This would be a very slight performance improvement, since would avoid extra equality checks on individual props each time mapStateToProps is called.

You may wish to override areMergedPropsEqual to implement a deepEqual if your selectors produce complex props. ex: nested objects, new arrays, etc. (The deep equal check should be faster than just re-rendering.)

Conclusion

Redux’s connect function within the container folder is in my opinion the most essential aspect of a React/Redux application. Now there are other ways to connect everything up without using Connect(), however this is by far the easiest way. It truly is the bridge between dumb React components and the Flux data flow of Redux.

Here is a final example that shows the connect function :

import {connect} from 'react-redux';import Template from '../components/Template.js';import {actionCreator1, actionCreator2} from '../actions/main.js';const mapStateToProps = ({ reducer1, reducer2}) =>
({reducer1, reducer2 });
const bindActionsToDispatch = dispatch =>
(
{
actionCreator1 : () => {dispatch(actionCreator1())},
actionCreator2 : (e) => {dispatch(actionCreator2(e))}
}
);
const mergeProps = (state, actions, {notebook, note}) => ({
...state,
...actions,
onLoad: notebook && note
? () => {
return (
actions.initActiveNotebookAndNote({notebook, note})
)
}
: null
});
connect(mapStateToProps, bindActionsToDispatch, mergeProps, { withRef: true })(Template);

Follow me on twitter! @iamtylerwclark

--

--

tyler clark
MoFed
Writer for

Father/Husband, Front-End Dev, Sports Fanatic, Mormon, Sightseer, Car Enthusiast, Ambitious, Always a Student