📜 ⬆️ ⬇️

Using connect () from the react-redux package

The article, the translation of which we publish today, will deal with 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 this is not the case, 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, the need arises 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 application state. She offers the developer understandable 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 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 status 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 Redux state using react-redux.

The react-redux package has a very simple interface. In particular, the most interesting thing about 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(( <Provider store={store}>   <AppRootComponent /> </Provider> ), rootElement); 

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

When do you need to use connect ()?


â–ŤCreation of container components


As already mentioned, the react-redux connect() API is 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 will not need the connect() function if you only create 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 the connect() function 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 login to view profile.</div> ) } const mapStateToProps = function(state) { return {   profile: state.user.profile,   loggedIn: state.auth.loggedIn } } export default connect(mapStateToProps)(ProfileContainer); 

â–Ť Eliminating the need for manual subscription to the Redux repository


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

In the following example, we are trying to manually create the 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'; 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; 

The connect() function, in addition, gives the developer additional flexibility, allowing you to customize container components to obtain dynamic properties based on the properties originally passed 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, then connect() makes it easy to specify the specific repository to which the container component should be connected.

Connect () anatomy


The connect() function provided by the react-redux package can take up to four arguments, each of which is optional. After the call to connect() called, a higher-order component is returned that 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 signature of the connect() function:

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

MapArgument mapStateToProps


The mapStateToProps argument is a function that returns either a regular object or another function. Passing this connect() argument causes the container component to subscribe to Redux storage updates. This means that the mapStateToProps function will be called each time the state of the repository changes. If you are not interested in tracking status updates, pass connect() as the value of this argument undefined or null .

The mapStateToProps function mapStateToProps declared 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 } } export default connect(mapStateToProps)(ProfileComponent); 

If a normal object is returned from mapStateToProps , then the returned stateProps object stateProps combined with the properties of the component. You can access these properties in a component like this:

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

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

MapArgument mapDispatchToProps


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

 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' }); 

Now consider the various options for using mapDispatchToProps .

Standard Default Implementation


If you do not use your own implementation of mapDispatchToProps , represented by an object or function, the standard implementation will be used, using which you implement the dispatch() storage method 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'; function Comment(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>     <button type="button" onClick={editComment}>Edit Comment</button>     <button type="button" onClick={removeComment}>Remove Comment</button>   </div> ) } export default connect()(Comment); 

Object transfer


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

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

 import React from 'react'; import { connect } from 'react-redux'; import { updateComment, deleteComment } from './actions'; function Comment(props) { const { id, content } = props.comment; // ,   ,   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> ) } //     const mapDispatchToProps = { updatePostComment: updateComment, deletePostComment: deleteComment } export default connect(null, mapDispatchToProps)(Comment); 

Transfer function


When using the function mapDispatchToProps as an argument mapDispatchToProps the programmer must take care of returning the dispatchProps object, which binds the action generators using the dispatch() method of the repository. This function takes, as the first parameter, the dispatch() storage method. As in the case with mapStateToProps , the function can also take the optional second parameter ownProps , which 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 of mapDispatchToProps , which can be useful for the purposes of improving rendering performance and memoization.

The bindActionCreators() helper function can be used inside this function to bind action generators to the dispatch() repository method.

The following example illustrates the use of the function in the mapDispatchToProps role. It also demonstrates bindActionCreators() work with the bindActionCreators() helper function used to bind action generators to work with comments to props.actions of the React component:

 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; //    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); 

â–ŤArgument mergeProps


If the connect() function is given the argument mergeProps , then it is a function that takes the following three parameters:


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() does not pass this function, then its standard implementation is used:

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

â–ŤArgument representing an object with parameters


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

Here is the documentation page that you can read about 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 normal 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 NewComment , which 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:

 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); 

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 the value of the component's store property:

 import React from 'react'; import store from './reduxStore'; import NewComment from './components/NewComment'; function CommentsApp(props) { return <NewComment store={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 storage.

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

â–ŤOrganize access to ownProps


As already mentioned, the mapStateToProps and mapDispatchToProps functions mapStateToProps to 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 ownProps will not be transmitted. But if the function is declared with the absence of required parameters or, at least, with 2 parameters, its ownProps will 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, ownProps passed, 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 }; 

There is only one parameter, state . As a result, arguments[1] is set to undefined because ownProps not passed.

Function declaration with a default parameter


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

There is only one required parameter, state , since the second parameter, ownProps , is optional due to the default value for it. As a result, since there is only one mandatory parameter, ownProps not 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 }; 

Everything is arranged very simply. Namely, in such a situation, ownProps transferred because the function is declared with two mandatory parameters.

Results


Having mastered this material, you learned about when and how to use the connect() API provided by the react-redux package and designed to create container components that are connected to the Redux state. Here we talked in some detail about the device's connect() function and how to work 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?

Source: https://habr.com/ru/post/423157/


All Articles