Using the connect () function from the react-redux package

Original author: Glad Chinda
  • Transfer
The article, the translation of which we publish today, will discuss how to create container components in React applications that are related to the state of Redux. This material is based on the description of the state control mechanism in React using the react-redux package . It is assumed that you already have a basic understanding of the architecture and library APIs that we will talk about. If not, refer to the React and Redux documentation .

image

About state management in JavaScript applications


React provides the developer with two basic mechanisms for transferring data to components. These are properties (props) and state (state). Properties are read-only and allow parent components to pass attributes to child components. A state is a local entity encapsulated inside a component, which can change at any time during the component's life cycle.

Since state is an extremely useful mechanism used to create powerful dynamic React applications, it is necessary to properly manage it. Currently there are several libraries that provide a well-structured architecture for managing the state of applications. Among them - Flux , Redux , MobX .

Redux is a library designed to create containers used to store the state of an application. It offers the developer clear state management tools that behave predictably. This library is suitable for applications written in pure JavaScript, and for projects that have been developed using some frameworks. Redux is small, but it also allows you to write robust applications that work in different environments.

Here's how to create Redux repositories:

import { createStore } from 'redux';
const initialState = {
    auth: { loggedIn: false }
}
const store = createStore((state = initialState, action) => {
    switch (action.type) {
        case "LOG_IN": 
            return { ...state, auth: { loggedIn: true } };
            break;
        case "LOG_OUT":
            return { ...state, auth: { loggedIn: false } };
            break;
        default:
            return state;
            break;
    }
    
})

React-redux package


The react-redux package provides React bindings for a Redux state container, making it extremely easy to connect a React application to a Redux repository. This allows you to separate the components of a React application based on their connection to the repository. Namely, we are talking about the following types of components:

  1. Presentation components. They are responsible only for the appearance of the application and are not aware of the state of Redux. They receive data through properties and can call callbacks, which are also passed to them through properties.
  2. Components containers. They are responsible for the operation of the internal mechanisms of the application and interact with the state of Redux. They are often created using react-redux, they can dispatch Redux actions. In addition, they subscribe to state changes.

Details about this approach to the division of responsibility of the components can be found here . In this material, we will mainly talk about container components connected to the state of Redux using react-redux.

The react-redux package has a very simple interface. In particular, the most interesting in this interface comes down to the following:

  1. <Provider store> - allows you to create a wrapper for a React application and make Redux state accessible to all container components in its hierarchy.
  2. connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])- allows you to create higher order components. This is needed to create container components based on React base components.

Install react-redux to use this package in the project as follows:

npm install react-redux --save

Based on the assumption that you have already configured the Redux repository for your React application, here is an example of connecting the application to the Redux repository:

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((
  <Providerstore={store}>
    <AppRootComponent />
  </Provider>
), rootElement);

Now you can create container components that are connected to the Redux repository. This is done within the hierarchy AppRootComponentusing the API connect().

When do you need to use connect ()?


▍Creation of container components


As already mentioned, the react-redux API is connect()used to create container components that are connected to the Redux repository. The storage to which the connection is made is obtained from the uppermost ancestor of the component using the React context mechanism. You connect()do not need the function if you create only the presentation components.

If you, in the React component, need to get data from the repository, or you need to dispatch actions, or you need to do both, you can convert a regular component into a container component by wrapping it into a higher-order component returned by a function connect()from react-redux . Here's what it looks like:

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 logintoview profile.</div>
  )
}
const mapStateToProps = function(state) {
  return {
    profile: state.user.profile,
    loggedIn: state.auth.loggedIn
  }
}
export defaultconnect(mapStateToProps)(ProfileContainer);

▍ Eliminating the need to manually subscribe to the Redux repository


You can create the container component yourself and manually sign the component on the Redux repository using the command store.subscribe(). However, using the function connect()means applying some improvements and performance optimizations that you may not be able to use when using other mechanisms.

In the following example, we are trying to manually create a container component and connect it to the Redux repository, subscribing to it. Here we strive to implement the same functionality, which is shown in the previous example.

import React, { Component } from 'react';
import store from './reduxStore';
import Profile from './components/Profile';
classProfileContainerextendsComponent{
  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;

The function connect(), in addition, gives the developer additional flexibility, allowing you to customize container components to obtain dynamic properties based on the properties originally transferred to them. This is very useful for getting samples from a state based on properties, or for binding action generators to a specific variable from properties.

If your React application uses multiple Redux repositories, it connect()allows you to easily specify the specific repository to which the container component should be connected.

Connect () anatomy


The function connect()provided by the react-redux package can take up to four arguments, each of which is optional. After calling the function connect(), a higher-order component is returned, which can be used to wrap any React component.

Since the function returns a higher-order component, it must be called again, passing the base React component in order to convert it to a container component:

const ContainerComponent = connect()(BaseComponent);

Here is the function signature connect():

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

MapArgument mapStateToProps


An argument mapStateToPropsis a function that returns either a regular object or another function. Passing this argument connect()causes the container component to subscribe to the Redux repository updates. This means that the function mapStateToPropswill be called every time the state of the storage changes. If you are tracking status updates are not interested, pass connect()the value of this argument undefinedor null.

The function is mapStateToPropsdeclared with two parameters, the second of which is optional. The first parameter represents the current state of the Redux repository. The second parameter, if passed, is the object of the properties passed to the component:

const mapStateToProps = function(state) {
  return {
    profile: state.user.profile,
    loggedIn: state.auth.loggedIn
  }
}
exportdefault connect(mapStateToProps)(ProfileComponent);

If a mapStateToPropsnormal object is returned from , the returned object is statePropscombined with the properties of the component. You can access these properties in a component like this:

functionProfileComponent(props) {
  return (
    props.loggedIn
      ? <Profileprofile={props.profile} />
      : <div>Please login to view profile.</div>
  )
}

If it mapStateToPropsreturns a function, then this function is used as mapStateToPropsfor each component instance. This can be useful for improving rendering performance and for memoization.

MapArgument mapDispatchToProps


An argument mapDispatchToPropscan be either an object or a function that returns either a regular object or another function. In order to better illustrate the work mapDispatchToProps, we need action generators. Suppose we have the following generators:

exportconst writeComment = (comment) => ({
  comment,
  type: 'WRITE_COMMENT'
});
exportconst updateComment = (id, comment) => ({
  id,
  comment,
  type: 'UPDATE_COMMENT'
});
exportconst deleteComment = (id) => ({
  id,
  type: 'DELETE_COMMENT'
});

Now consider the various uses mapDispatchToProps.

Standard default implementation


If you do not use your own implementation mapDispatchToProps, represented by an object or a function, the standard implementation will be used, using which the storage method is implemented dispatch()as a property for the component. You can use this property in the component as follows:

import React from'react';
import { connect } from'react-redux';
import { updateComment, deleteComment } from'./actions';
functionComment(props) {
  const { id, content } = props.comment;
  
  // Вызов действий через props.dispatch()
  const editComment = () => props.dispatch(updateComment(id, content));
  const removeComment = () => props.dispatch(deleteComment(id));
  
  return (
    <div>
      <p>{ content }</p>
      <buttontype="button"onClick={editComment}>Edit Comment</button>
      <buttontype="button"onClick={removeComment}>Remove Comment</button>
    </div>
  )
}
exportdefault connect()(Comment);

Object transfer


If an mapDispatchToPropsobject is used as an argument , then each function in the object will be interpreted as a Redux action generator and wrapped in a call to the storage method dispatch(), which will allow it to be called directly. The resulting object with the action generators dispatchPropswill be combined with the properties of the component.

The following example shows an example of constructing an argument mapDispatchToPropsthat represents an object with action generators, and how generators can be used as properties of the React component:

import React from'react';
import { connect } from'react-redux';
import { updateComment, deleteComment } from'./actions';
functionComment(props) {
  const { id, content } = props.comment;
  
  // Действия, представленные свойствами компонента, вызываются напрямую
  const editComment = () => props.updatePostComment(id, content);
  const removeComment = () => props.deletePostComment(id);
  
  return (
    <div>
      <p>{ content }</p>
      <buttontype="button"onClick={editComment}>Edit Comment</button>
      <buttontype="button"onClick={removeComment}>Remove Comment</button>
    </div>
  )
}
// Объект с генераторами действийconst mapDispatchToProps = {
  updatePostComment: updateComment,
  deletePostComment: deleteComment
}
exportdefault connect(null, mapDispatchToProps)(Comment);

Transfer function


When using a mapDispatchToPropsfunction as an argument, the programmer must independently take care of returning the object dispatchPropsthat binds the action generators using the storage method dispatch(). This function accepts, as the first parameter, a storage method dispatch(). As in the case of c mapStateToProps, the function can also take an optional second parameter ownPropsthat describes the mapping with the original properties passed to the component.

If this function returns another function, then the returned function is used in the role mapDispatchToProps, which can be useful for the purposes of improving rendering performance and memoization.

Helper functionbindActionCreators()Redux can be used inside this function to bind action generators to a storage method dispatch().

The following example illustrates the use, in a role mapDispatchToProps, of a function. It also demonstrates bindActionCreators()how to work with an auxiliary function used to bind action generators to work with comments to props.actionsthe React component:

import React from'react';
import { connect } from'react-redux';
import { bindActionCreators } from'redux';
import * as commentActions from'./actions';
functionComment(props) {
  const { id, content } = props.comment;
  const { updateComment, deleteComment } = props.actions;
  
  // Вызов действий из props.actions
  const editComment = () => updateComment(id, content);
  const removeComment = () => deleteComment(id);
  
  return (
    <div>
      <p>{ content }</p>
      <buttontype="button"onClick={editComment}>Edit Comment</button>
      <buttontype="button"onClick={removeComment}>Remove Comment</button>
    </div>
  )
}
const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators(commentActions, dispatch)
  }
}
exportdefault connect(null, mapDispatchToProps)(Comment);

▍Argument mergeProps


If an connect()argument is passed to the function mergeProps, it is a function that takes the following three parameters:

  • stateProps- the property object returned from the call mapStateToProps().
  • dispatchProps- property object with action generators from mapDispatchToProps().
  • ownProps - the initial properties obtained by the component.

This function returns a simple object with properties that will be passed to the wrapped component. This is useful for conditional mapping of the state of the Redux repository state or property-based action generators.

If connect()this function is not passed, then its standard implementation is used:

const mergeProps = (stateProps, dispatchProps, ownProps) => {
  returnObject.assign({}, ownProps, stateProps, dispatchProps)
}

▍Argument representing an object with parameters


An optional object passed to the function connect()as the fourth argument contains parameters intended to change the behavior of this function. So, connect()is a special implementation of the function connectAdvanced(), it takes most of the parameters available connectAdvanced(), as well as some additional parameters.

Here is the documentation page, after reviewing which, you can find out which parameters you can use with connect(), and how they modify the behavior of this function.

Using the connect () Function


▍Create a repository


Before converting a regular React component to a container component using connect(), you need to create a Redux repository to which this component will be connected.

Suppose that we have a container component NewCommentthat is used to add new comments to a publication, and, in addition, displays a button to send a comment. The code describing this component might look like this:

importReact from 'react';
import { connect } from 'react-redux';
classNewCommentextendsReact.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}>SubmitComment</button>
      </div>
    )
  }
  
}
export default connect()(NewComment);

In order for this component to be used in the application, it will be necessary to describe the Redux repository to which this component must be connected. Otherwise, an error will occur. This can be done in two ways, which we will now consider.

Setting the store property in a container component


The first way to equip a component with Redux storage is to pass a reference to such storage as a value of a storecomponent property :

import React from'react';
import store from'./reduxStore';
import NewComment from'./components/NewComment';
functionCommentsApp(props) {
  return<NewCommentstore={store} />
}

Setting the store property in the <Provider> component


If you want to set up a Redux repository for an application only once, then you will be interested in the method that we will now consider. It is usually suitable for applications that use only one Redux repository.

The react-redux package provides a developer with components <Provider>that can be used to wrap the root component of an application. It takes property store. It is assumed that it is a link to the Redux repository, which is planned to be used in the application. The property is storepassed, in accordance with the application hierarchy, to container components using the React 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';
functionCommentsApp(props) {
  return<NewComment />
}
ReactDOM.render((
  <Providerstore={store}>
    <CommentsApp />
  </Provider>
), document.getElementById('root'))

▍Organize access to ownProps


As already mentioned, the functions mapStateToPropsand mapDispatchToProps, passed connect(), can be declared with the second parameter ownProps, which is the properties of the component.
However, there is one problem. If the number of required parameters of the declared function is less than 2, then ownPropsit will not be transmitted. But if the function is declared with the absence of the required parameters or, at least, with 2 parameters, it ownPropswill be transferred.

Consider several options for working with ownProps.

Function declaration without parameters


const mapStateToProps = function() {
  console.log(arguments[0]); // state
  console.log(arguments[1]); // ownProps
};

In this situation, it is ownPropspassed because the function is declared without the required parameters. As a result, the following code written using the new syntax of the remaining ES6 parameters will work:

const mapStateToProps = function(...args){
  console.log(args[0]); // state
  console.log(args[1]); // ownProps
};

Function declaration with one parameter


Consider the following example:

const mapStateToProps = function(state) {
  console.log(state); // state
  console.log(arguments[1]); // undefined
};

Here there is only one option state. As a result, arguments[1]takes the value undefineddue to the fact that it is ownPropsnot transmitted.

Function declaration with a default parameter


const mapStateToProps = function(state, ownProps = {}) {
  console.log(state); // state
  console.log(ownProps); // {}
};

There is only one mandatory parameter here, statesince the second parameter ownProps,, is optional due to the fact that it has a default value. As a result, since there is only one mandatory parameter, it is ownPropsnot passed, and mapping is performed with the default value that was assigned to it, that is, with an empty object.

Function declaration with two parameters


const mapStateToProps = function(state, ownProps) {
  console.log(state); // state
  console.log(ownProps); // ownProps
};

It is all very simple. Namely, in such a situation, a transfer is ownPropsmade because the function is declared with two mandatory parameters.

Results


Having mastered this material, you learned about when and how to use the API connect()provided by the react-redux package and designed to create container components that are connected to the Redux state. Here we have described in some detail about the device function connect()and about working with it, however, if you want to learn more about this mechanism, in particular - to familiarize yourself with the options for using it - take a look at this section of the react-redux documentation.

Dear readers! Do you use the react-redux package in your projects?


Also popular now: