connect()
: When and how to use itEditor’s note: This article was last updated 16 June 2022 to include the use of the useDispatch
and useSelector
Hooks in React functional components.
React provides two major mechanisms for providing data to components, props and state. Props are read-only and allow a parent component to pass attributes to a child component. State is local and encapsulated within the component. It can change at any time in the component’s lifecycle.
Since state is a very powerful mechanism for building dynamic React apps, proper state management is paramount. Several libraries already exist that provide a well-structured architecture for managing application state, like Flux, Redux, and MobX to name a few.
In this guide, we’ll show you how to manage state in React applications with Redux using React Redux. We’ll assume you already have a fundamental understanding of React and the Redux architecture and API. Let’s get started!
connect()
?connect()
in Redux
connect()
work?
connect()
ownProps
Redux is a predictable state container for JavaScript apps, ranging from vanilla apps to framework apps. Redux has a very tiny footprint, but it still allows you to write consistent apps that can run in any environment:
If you’re new to the Redux community and feeling overwhelmed by the many design patterns and critiques surrounding Redux, you should try out Redux Toolkit, the official, opinionated, batteries-included toolset for efficient Redux development.
Redux Toolkit has eliminated many of the criticisms of Redux, helping to bridge knowledge gaps around Redux best practices and patterns.
connect()
?The React Redux package provides React bindings for the Redux state container, making it very easy to connect a React application to a Redux store. This allows you to separate your React application components as presentational and container components based on their connection to the Redux store.
Presentational components are only concerned with how things look. They are not aware of the Redux state. Presentational components get their data from props and may trigger callbacks passed to them via props. On the other hand, container components are responsible for how things work and are fully aware of the Redux state. They are often created using React Redux and may dispatch Redux actions. They also subscribe to changes in the Redux state.
It’s worth noting that in 2019, Redux author Dan Abramov advised against this approach in a revision to his 2015 blog post on presentational and container components. As of version 7.1, Redux supports React Hooks, meaning you can use Redux with Hooks in your functional components instead of using Redux connect()
.
With that said, it’s useful to understand the core concept of separating business logic from presentational components because it can simplify solving a lot of complex problems. In this guide, we’ll focus on container components that are connected to the Redux state using React Redux.
The React Redux package exposes a very simple interface. You only need to concern yourself with two components. For one, the <Provider store>
wraps the React application and makes the Redux state available to all container components in the application’s hierarchy. On the other hand, connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
creates a higher order component for making container components out of base React components.
You can install React Redux in your project as follows:
npm install react-redux --save
Given that you already have a Redux store set up for your React application, you can connect the app to the Redux store with the following code:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import createStore from './createReduxStore'; const store = createStore(); const rootElement = document.getElementById('root'); ReactDOM.render(( <Provider store={store}> <AppRootComponent /> </Provider> ), rootElement);
With this setup, you can now create container components that are connected to the Redux store within the hierarchy of the AppRootComponent
using the connect()
API.
connect()
in ReduxLet’s take a look at some common scenarios where it’s appropriate to use connect()
.
The React Redux connect()
API is used for creating container elements that are connected to the Redux store. The Redux store is derived from the topmost ancestor of the component using React Context. If you are only creating a presentational component, you have no need for connect()
.
Whether you want to get data from the Redux store, dispatch actions on the Redux store, or do both in your React component, you can make the component a container component by wrapping it in a higher order component returned by connect()
:
import React from 'react'; import { connect } from 'react-redux'; import Profile from './components/Profile'; function ProfileContainer(props) { return ( props.loggedIn ? <Profile profile={props.profile} /> : <div>Please login to view profile.</div> ) } const mapStateToProps = function(state) { return { profile: state.user.profile, loggedIn: state.auth.loggedIn } } export default connect(mapStateToProps)(ProfileContainer);
You could create a container component yourself and manually subscribe the component to the Redux store using store.subscribe()
. However, using connect()
comes with some performance improvements and optimizations that you may not be able to implement in your application.
In the following code snippet, we attempt to manually create a container component and connect it to the Redux store. Like in the previous code snippet, we are subscribing to the store to achieve a similar functionality:
import React, { Component } from 'react'; import store from './reduxStore'; import Profile from './components/Profile'; class ProfileContainer extends Component { state = this.getCurrentStateFromStore() getCurrentStateFromStore() { return { profile: store.getState().user.profile, loggedIn: store.getState().auth.loggedIn } } updateStateFromStore = () => { const currentState = this.getCurrentStateFromStore(); if (this.state !== currentState) { this.setState(currentState); } } componentDidMount() { this.unsubscribeStore = store.subscribe(this.updateStateFromStore); } componentWillUnmount() { this.unsubscribeStore(); } render() { const { loggedIn, profile } = this.state; return ( loggedIn ? <Profile profile={profile} /> : <div>Please login to view profile.</div> ) } } export default ProfileContainer;
connect()
also provides additional flexibility, allowing you to configure container components to receive dynamic props based on the props initially passed to them. This is useful for selecting a slice of the Redux state based on props or binding action creators to a particular variable from props.
It is best practice to use only a single Redux store per app, but if your project requires multiple Redux stores, connect()
allows you to easily specify which store a container component should be connected to.
Many developers feel that React Context is a replacement for Redux. In my opinion, that’s not the case. Context is not designed for global state management; rather, Context is for avoiding prop drilling through the component tree.
For medium and large-scale applications, global state management is the key to maintainability and scalability. In fact, Connect and Redux generally use React Context under the hood to supply data to the connected components. It adds a few extra layers of immutability and memoization, making apps more performant.
connect()
work?The connect()
function provided by React Redux can take up to four arguments, all of which are optional. Calling the connect()
function returns a higher order component, which can be used to wrap any React component.
Since a higher order component is returned by connect()
, it has to be invoked again with the base React component to convert it to a container component:
const ContainerComponent = connect()(BaseComponent); // Here is the signature of the connect() function: connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps
in React ReduxThe argument below is a function that returns either a plain object or another function:
mapStateToProps(state, [ownProps]) => stateProps
Passing this argument subscribes the container component to the Redux store updates, meaning that the mapStateToProps
function will be invoked each time the store updates. If you are not interested in store updates, you can leave it as undefined or null.
mapStateToProps
is declared with two parameters. The first parameter is the current state of the Redux store. The second parameter, which is optional, is an object of the props passed to the component:
const mapStateToProps = function(state) { return { profile: state.user.profile, loggedIn: state.auth.loggedIn } } export default connect(mapStateToProps)(ProfileComponent);
If a plain object is returned from mapStateToProps
, the returned stateProps
object is merged into the component’s props. You can access these props in the component as follows:
function ProfileComponent(props) { return ( props.loggedIn ? <Profile profile={props.profile} /> : <div>Please login to view profile.</div> ) }
However, if a function is returned, that function is used as mapStateToProps
for each instance of the component. This can be useful for improving the rendering performance and for memoization.
To make your apps more performant, Redux recommends using Reselect, which helps you select only the required chunk of data from a global store. Selectors can compute derived data to keep your store minimal and also add memoization to selection for better application performance.
The argument below can either be an object or a function that returns a plain object or another function:
mapDispatchToProps(dispatch, [ownProps]) => dispatchProps
To better illustrate how mapDispatchToProps
works, you will need to have some action creators. For example, say you have the following action creators. You can use the dispatch prop in your component as follows:
export const writeComment = (comment) => ({ comment, type: 'WRITE_COMMENT' }); export const updateComment = (id, comment) => ({ id, comment, type: 'UPDATE_COMMENT' }); export const deleteComment = (id) => ({ id, type: 'DELETE_COMMENT' });
If you don’t supply your own mapDispatchToProps
object or function, the default implementation will be used, which simply injects the store’s dispatch method as a prop to the component:
import React from 'react'; import { connect } from 'react-redux'; import { updateComment, deleteComment } from './actions'; function Comment(props) { const { id, content } = props.comment; // Invoking the actions via props.dispatch() const editComment = () => props.dispatch(updateComment(id, content)); const removeComment = () => props.dispatch(deleteComment(id)); return ( <div> <p>{ content }</p> <button type="button" onClick={editComment}>Edit Comment</button> <button type="button" onClick={removeComment}>Remove Comment</button> </div> ) } export default connect()(Comment);
If an object is passed for this argument, each function in the object will be taken to be a Redux action creator and wrapped into a call to the store’s dispatch method. Therefore, it can be called directly. The resulting dispatchProps
object of action creators will be merged into the component’s props.
The following code snippet shows how you can define mapDispatchToProps
by supplying an object of action creators and how the action creators can be used as props to your React component:
import React from 'react'; import { connect } from 'react-redux'; import { updateComment, deleteComment } from './actions'; function Comment(props) { const { id, content } = props.comment; // Invoking the actions directly as component props const editComment = () => props.updatePostComment(id, content); const removeComment = () => props.deletePostComment(id); return ( <div> <p>{ content }</p> <button type="button" onClick={editComment}>Edit Comment</button> <button type="button" onClick={removeComment}>Remove Comment</button> </div> ) } // Object of action creators const mapDispatchToProps = { updatePostComment: updateComment, deletePostComment: deleteComment } export default connect(null, mapDispatchToProps)(Comment);
If a function is passed, it is left to you to return an object of dispatchProps
that binds action creators using the store’s dispatch method. The function takes the store’s dispatch as its first parameter. Like with mapStateToProps
, it can also take an optional ownProps
as a second parameter that maps to the original props passed to the component.
If this function returns another function, then the returned function is used as mapDispatchToProps
instead, which can be useful for improving rendering performance and memoization.
The bindActionCreators()
helper provided by Redux can be used within this function to bind action creators to the store’s dispatch method.
The following code snippet shows how you can define mapDispatchToProps
by supplying a function and how you can use the bindActionCreators()
helper to bind the comment action creators to a React component’s props.actions
:
import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as commentActions from './actions'; function Comment(props) { const { id, content } = props.comment; const { updateComment, deleteComment } = props.actions; // Invoking the actions from props.actions const editComment = () => updateComment(id, content); const removeComment = () => deleteComment(id); return ( <div> <p>{ content }</p> <button type="button" onClick={editComment}>Edit Comment</button> <button type="button" onClick={removeComment}>Remove Comment</button> </div> ) } const mapDispatchToProps = (dispatch) => { return { actions: bindActionCreators(commentActions, dispatch) } } export default connect(null, mapDispatchToProps)(Comment); mergeProps(stateProps, dispatchProps, ownProps) => props
This argument, if passed, is a function that takes three parameters:
stateProps
: The props object returned from a call to mapStateToProps()
dispatchProps
:  The action creator props object from mapDispatchToProps()
ownProps
: The original props received by the componentThe function returns a plain object of props that will be passed to the wrapped component. This is useful for conditionally mapping part of the Redux store’s state or action creators based on props.
When this function is not supplied, the default implementation is as follows:
const mergeProps = (stateProps, dispatchProps, ownProps) => { return Object.assign({}, ownProps, stateProps, dispatchProps) }
options
The options
object, if specified, contains options for modifying the behavior of connect()
. connect()
is a special implementation of connectAdvanced()
that accepts most of the options available to connectAdvanced()
with some additional options.
You can refer to the options
object documentation to see all the options available for connect()
and learn how they can modify its behavior. Diving into this API will help you understand how Redux works under the hood and what benefits we gain from using it over other state management patterns.
Let’s explore the API for the options
object. It uses the context? object
as well as a pure
boolean. It also allows you to pass custom contexts beside the global store to the container.
The pure
boolean allows Redux to understand whether the container is pure, meaning it doesn’t depend on anything besides its props and state. By default, it is true, but we can leverage the following API to override the default checks:
areStatesEqual?: (next: Object, prev: Object) => boolean; areOwnPropsEqual?: (next: Object, prev: Object) => boolean; areStatePropsEqual?: (next: Object, prev: Object) => boolean; areMergedPropsEqual?: (next: Object, prev: Object) => boolean;
By default, all of these methods use shallow comparison to determine the change. We can override it for performance-intensive containers, but this should be done cautiously because it can break the container at any point.
Finally, the options
object API uses the forwardRef: boolean
, which is useful when you want to access instance methods of the container from outside.
connect()
Before converting a regular React component to a container component using connect()
, you have to specify the Redux store to which the component will be connected.
Assume that you have a container component named NewComment
for adding a new comment to a post and also showing a button to submit the comment. The component might look like the following code snippet:
import React from 'react'; import { connect } from 'react-redux'; class NewComment extends React.Component { input = null writeComment = evt => { evt.preventDefault(); const comment = this.input.value; comment && this.props.dispatch({ type: 'WRITE_COMMENT', comment }); } render() { const { id, content } = this.props.comment; return ( <div> <input type="text" ref={e => this.input = e} placeholder="Write a comment" /> <button type="button" onClick={this.writeComment}>Submit Comment</button> </div> ) } } export default connect()(NewComment);
For you to actually use this component in your application, you will have to specify which Redux store the component will be connected to. Otherwise, you will get an error. This can be done in one of two ways.
For one, you can specify the Redux store on the component by passing a reference to the Redux store as the value of the store
prop of the component:
import React from 'react'; import store from './reduxStore'; import NewComment from './components/NewComment'; function CommentsApp(props) { return <NewComment store={store} /> }
<Provider>
componentIf you wish to only set the Redux store once for your application, then setting the store prop on a <Provider>
component is the way to go. This is usually the case for apps that use only one Redux store.
React Redux provides a <Provider>
component that can be used to wrap the root application component. It accepts a store
prop that expects a reference to the Redux store you want to use for your application. The store
is passed down to container components down the app’s hierarchy using React’s Context mechanism:
import React from 'react'; import ReactDOM from 'react-dom'; import store from './reduxStore'; import { Provider } from 'react-redux'; import NewComment from './components/NewComment'; function CommentsApp(props) { return <NewComment /> } ReactDOM.render(( <Provider store={store}> <CommentsApp /> </Provider> ), document.getElementById('root'))
ownProps
As stated earlier, the mapStateToProps
and mapDispatchToProps
functions passed to connect()
can be declared with the ownProps
of the component as the second parameter.
However, there is a caveat. If the number of mandatory parameters of the declared function is less than two, then ownProps
will never be passed. But, if the function is declared with no mandatory parameters or at least two parameters, then ownProps
is passed. Let’s review a few potential scenarios.
const mapStateToProps = function() { console.log(arguments[0]); // state console.log(arguments[1]); // ownProps };
In the example above, ownProps
is passed because the function is declared with no mandatory parameters. Therefore, the code below will also work in a similar fashion, using the ES6 rest parameters syntax:
const mapStateToProps = function(...args) { console.log(args[0]); // state console.log(args[1]); // ownProps };
In the code below, there is only one parameter, state
. Therefore, arguments[1]
is undefined
because ownProps
is not passed:
const mapStateToProps = function(state) { console.log(state); // state console.log(arguments[1]); // undefined };
In the code below, there is only one mandatory parameter, state
. The second ownProps
parameter is optional since a default value has been specified for it. Since there is only one mandatory parameter, ownProps
is not passed, and as a result, it maps to the default value that was assigned to it, {}
:
const mapStateToProps = function(state, ownProps = {}) { console.log(state); // state console.log(ownProps); // {} };
const mapStateToProps = function(state, ownProps) { console.log(state); // state console.log(ownProps); // ownProps };
The code above is pretty straightforward. ownProps
is passed since the function is declared with two mandatory parameters.
The React Redux package introduced the useSelector
and useDispatch
Hooks, which we can use to easily hook into the Redux store and dispatch actions to the store without using the connect
function:
import { useSelector, useDispatch } from 'react-redux'
The useAppSelector
is used to read a value from the store state, and it also subscribes to the state’s updates. The useDispatch
Hook is used to dispatch actions to the reducer functions.
Now, let’s demonstrate how to use these both hooks by rewriting our NewComment
component, which currently looks like the following code:
import React from 'react'; import { connect } from 'react-redux'; class NewComment extends React.Component { input = null writeComment = evt => { evt.preventDefault(); const comment = this.input.value; comment && this.props.dispatch({ type: 'WRITE_COMMENT', comment }); } render() { const { id, content } = this.props.comment; return ( <div> <input type="text" ref={e => this.input = e} placeholder="Write a comment" /> <button type="button" onClick={this.writeComment}>Submit Comment</button> </div> ) } } export default connect()(NewComment);
Let’s rewrite it using the React Redux Hooks useSelector
and useDispatch
:
import React, { useRef } from "react"; import { useSelector, useDispatch } from "react-redux"; function NewComment() { const input = useRef(); const dispatch = useDispatch(); const { id, content } = useSelector((state) => state.comment); const writeComment = (evt) => { evt.preventDefault(); const comment = input.current.value; comment && dispatch({ type: "WRITE_COMMENT", comment }); }; return ( <div> <input type="text" ref={input} placeholder="Write a comment" /> <button type="button" onClick={writeComment}> Submit Comment </button> </div> ); } export default NewComment;
In the code above, we converted the NewComment
component from a class component to a functional component. Hooks like useDipatch
and useSelector
are only used in React functional components.
Notice how we called the useDispatch
Hook, and it returns a dispatch
function. We use dispatch
to dispatch actions to the store. The useSelector
Hook is called with a callback function, which will receive the state.
See that in the callback function, we only returned the comment
state because that’s what we want to use in this component. With the React Redux Hooks, we removed the connect
function. There is no longer a need for it because useSelector
already returns a slice of the store state and subscribes to it.
Whenever the comment
state is changed, the useSelector
will be notified and will make the NewComment
component to re-render to display the new state.
In this guide, we’ve covered when and how to use the React Redux connect()
API to create container components that are connected to the Redux state. As mentioned, if you’re new to Redux, it is highly recommended to go with Redux Toolkit, which can help you get going quickly. We also briefly covered an alternative to the React Redux connect()
API, the useSelector
and useDispatch
Hooks, which work in React functional components.
While this guide covers much of the anatomy of the connect()
API and its usage, it did not extensively show use case examples. You can find more of these in this documentation.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]