Component communication in React (react18) 05 – redux ? react-redux (including data sharing)

Component communication in React (react18) 05 – redux? react-redux (including data sharing)

  • 1 Introduction
    • 1.1 Other ways of component communication in React
    • 1.2 Introduction to React-Redux
      • 1.2.1 A brief introduction to React-Redux
      • 1.2.2 Official website
    • 1.3 Install react-redux
  • 2. Example of simply rewriting redux
    • 2.1 Provide store
    • 2.2 Connection Components + UI component modification
      • 2.2.1 Connect Components
      • 2.2.2 Modify UI components
      • 2.2.3 See the effect
    • 2.3 Connect Components – Optimization (optimize container components)
    • 2.4 Optimizing container components (horrible simplification)
    • 2.5 Attached code
  • 3. Multiple reducers realize data sharing
    • 3.1 Introduction to combineReducers() function
    • 3.2 Example of multi-reducer integration
      • 3.2.1 The effect you want to achieve
      • 3.2.2 Project directory design structure
      • 3.2.3 Integrate three reducers (or split reducers)
      • 3.2.4 Three core files for each component
        • 3.2.4.1 Brief introduction – taking cat as an example
        • 3.2.4.2 About dog and petPark
    • 3.3 Realize data sharing
      • 3.3.1 Realize data sharing
    • 3.4 Attached core code
      • 3.4.1 Two actions
      • 3.4.2 Three reducers + one integrated reducer
      • 3.4.3 Three components
  • 4. Attached items

1. Preface

1.1 Other ways of component communication in React

  • Component communication 01 in React (react18) – props.
  • Component communication in React (react18) 02 – message subscription and publishing, unsubscription and unsubscription when uninstalling the component.
  • Component communication in React (react18) 03 – simply use Context to pass parameters deeply.
  • Component communication in React (react18) 04 – Getting started with redux.
    The code changes in this article are based on this (Introduction to Redux) article, so if you have questions about redux, please read this article.
  • Component communication in React (react18) 06 – redux-toolkit + react-redux.

1.2 Introduction to React-Redux

1.2.1 A brief introduction to React-Redux

  • React-Redux is the React binding library officially provided by Redux. Efficient and flexible.
    • react-redux is a Redux-based library designed for React application development, providing some React-specific features and components.
    • It provides a series of APIs and components to facilitate the use of Redux for state management in React components.
  • React-Redux is conceptually very simple. It subscribes to the Redux store, checks if the data required by the component has changed, and re-renders the component.
  • react-redux provides some React-specific functions, such as the connect function and Provider component, which are used to connect to the Redux store and pass state to React components.
    • The component provided by React Redux allows the Redux store to be used elsewhere in the application (i.e. the store only needs to be passed once in the entry file and can be obtained from other container components that require the store) ).

1.2.2 Official website

  • Reference official website:
    • Official website address: https://react-redux.js.org/.
    • On gitHub: https://github.com/reduxjs/react-redux.
    • Redux Chinese official website.
    • React Redux Chinese documentation.
  • Other blogs to learn about react-redux:
    • History and implementation of React-Redux.
  • For the connect API used below, go to the official website:
    https://cn.react-redux.js.org/tutorials/connect.

1.3 Install react-redux

  • The installation command is as follows:
    # If you use npm:
    npm install react-redux
    
    # Or if you use Yarn:
    yarn add react-redux
    

2. Example of simply rewriting redux

  • Note that this rewrite is based on the redux project version. For the redux version, see the following:
    Component communication in React (react18) 04 – Getting started with redux.

2.1 Provide store

  • The first step is to make the store visible to our application. To do this, we use the API provided by React Redux to wrap our application. :
    • First, give the rewritten directory structure
    • Then look at app.js and index.js

2.2 Connect Components + UI component modification

2.2.1 Connect Components

  • Let’s first look at how the official website explains it

  • Let’s simply write down the implementation effect first, and then optimize it later, as follows:

    import CountNumRedux from "../components/CountNumRedux";
    import {<!-- --> connect } from "react-redux";
    import store from '../redux/store'
    
    //If ownProps is not used here, it does not need to be passed. You can only pass state.
    const mapStateToProps = (state, ownProps) => ({<!-- -->
          // ...generate computed data based on state and custom ownProps
          /**
           * That is, the state is managed uniformly in the container component
           * If you use UI components, you can get them directly through props. This method is also equivalent to passing them through props.
           * If you listen for changes in state, call it whenever there is a change, and pass the state to the UI component through props
           */
          count:state
        // name:'McDull'
      });
    
      const mapDispatchToProps = ()=>({<!-- -->
        // ...usually an object filled with action creators
           addNumber:(number)=>{<!-- -->
               store.dispatch(
                   {<!-- --> type: 'INCREMENT', number:number }
               )
           },
           reduceNumber:(number)=>{<!-- -->
               store.dispatch(
                   {<!-- --> type: 'DECREMENT', number:number }
               )
           }
     });
    
      
    // // 1. `connect` returns a new function that receives the component to be wrapped:
    // const connectToStore = connect(mapStateToProps, mapDispatchToProps);
    
    // // 2. And the function returns the connected, wrapped component:
    // const ConnectedComponent = connectToStore(Component);
      
      // Usually we will do both in one step, like this:
    const CountNumContainer = connect(mapStateToProps, mapDispatchToProps)(CountNumRedux);
    
    export default CountNumContainer;
    

2.2.2 Modify UI components

  • as follows:

    import {<!-- --> createRef } from "react";
    // import store from '../redux/store'
    // import countAction from '../redux/countAction'
    
    function CountNumRedux(props){<!-- -->
        console.log(props);
    
        // const [count,setCount] = useState(0);
        const numberRef = createRef();
    
        function add(){<!-- -->
            let number = numberRef.current.value;
            // console.log(typeof number); //string
            // store.dispatch(countAction.incrementNum(parseInt(number)));
            props.addNumber(parseInt(number));
        }
    
        function subtract(){<!-- -->
            let number = parseInt(numberRef.current.value);
            props.reduceNumber(number);
        }
    
        // useEffect(()=>{<!-- -->
        // store.subscribe(()=>{<!-- -->
        // console.log('Subscribe to update, print 2-----', store.getState());
        // setCount(store.getState());
        // });
        // });
    
        return(
            <div>
                {<!-- -->/* The current number is: {count} & amp;nbsp; & amp;nbsp; & amp;nbsp; & amp;nbsp;
                The current number is: {store.getState()} */}
    
                The current value is: {<!-- -->props.count}
                <br />
                Floating number: <input type="number" ref={<!-- -->numberRef}/>
    
                <br /><br />
                <button onClick={<!-- -->add}>Click me to add the number</button> <br /><br />
                <button onClick={<!-- -->subtract}>Click me to subtract</button>
            </div>
        )
    }
    export default CountNumRedux;
    

2.2.3 See the effect

  • as follows:

2.3 Connecting Components – Optimization (optimizing container components)

  • Mainly optimize mapDispatchToProps and use encapsulated actions, as follows:

    import CountNumRedux from "../components/CountNumRedux";
    import {<!-- --> connect } from "react-redux";
    // import store from '../redux/store'
    import {<!-- -->incrementNum,decrementNum} from "../redux/countAction";
    
    
    const mapStateToProps = (state) => ({<!-- -->
          count:state
      });
    
    
    // const mapDispatchToProps = ()=>({<!-- -->
    // addNumber:(number)=>{<!-- -->
    // store.dispatch(
    // { type: 'INCREMENT', number:number }
    // )
    // },
    // reduceNumber:(number)=>{<!-- -->
    // store.dispatch(
    // { type: 'DECREMENT', number:number }
    // )
    // }
    // });
    
    /**
     * 1. dispatch: react-redux will pass dispatch in, so there is no need to introduce store to adjust it.
     * 2. Introduce the encapsulated action: countAction
     */
     const mapDispatchToProps = (dispatch)=>({<!-- -->
        addNumber:(number)=>{<!-- -->
            dispatch(incrementNum(number))
        },
        reduceNumber:(number)=>{<!-- -->
            dispatch(decrementNum(number))
        }
    });
    
    const CountNumContainer = connect(mapStateToProps, mapDispatchToProps)(CountNumRedux);
    
    export default CountNumContainer;
    

2.4 Optimizing container components (horrible simplification)

  • mapDispatchToProps: This parameter can be a function or an object.
    • The above are all written in function. After changing to object, the code is really too little!
    • Let’s take another look at what the official emphasized:
  • The simplified code is as follows:
    /**
     * Optimization 2
     */
    const mapDispatchToProps = {<!-- -->
        //Usually an object filled with action creators
        addNumber: incrementNum, //addNumber: is the method passed to the UI component through props, incrementNum: is the encapsulated action function
        reduceNumber: decrementNum
    }
    

    Compare:

2.5 with code

  • I won’t post the code under the redux file because there are no changes. If you need it, just go to the previous article. The others are as follows:
    • CountNumContainer.jsx
      import CountNumRedux from "../components/CountNumRedux";
      import {<!-- --> connect } from "react-redux";
      import {<!-- -->incrementNum,decrementNum} from "../redux/countAction";
      
      const mapStateToProps = (state) => ({<!-- -->
            count:state
        });
      
      const mapDispatchToProps = {<!-- -->
          //Usually an object filled with action creators
          addNumber: incrementNum, //addNumber: is the method passed to the UI component through props, incrementNum: is the encapsulated action function
          reduceNumber: decrementNum
      }
      
      const CountNumContainer = connect(mapStateToProps, mapDispatchToProps)(CountNumRedux);
      
      export default CountNumContainer;
      
    • CountNumRedux.jsx
      import {<!-- --> createRef } from "react";
      
      function CountNumRedux(props){<!-- -->
          console.log(props);
          
          const numberRef = createRef();
      
          function add(){<!-- -->
              let number = numberRef.current.value;
              props.addNumber(parseInt(number));
          }
      
          function subtract(){<!-- -->
              let number = parseInt(numberRef.current.value);
              props.reduceNumber(number);
          }
      
          return(
              <div>
                  {<!-- -->/* The current number is: {count} & amp;nbsp; & amp;nbsp; & amp;nbsp; & amp;nbsp;
                  The current number is: {store.getState()} */}
      
                  The current value is: {<!-- -->props.count}
                  <br />
                  Floating number: <input type="number" ref={<!-- -->numberRef}/>
      
                  <br /><br />
                  <button onClick={<!-- -->add}>Click me to add the number</button> <br /><br />
                  <button onClick={<!-- -->subtract}>Click me to subtract</button>
              </div>
          )
      }
      export default CountNumRedux;
      
    • App.js
      import CountNumContainer from './container/CountNumContainer.jsx'
      
      function App() {<!-- -->
        return (
          <div>
            {<!-- -->/* <CountNumRedux/> */}
            <CountNumContainer/>
          </div>
        );
      }
      
      export default App;
      
    • index.js
      import React from 'react';
      import ReactDOM from 'react-dom/client';
      import App from './App';
      
      import store from './redux/store';
      import {<!-- --> Provider } from 'react-redux';
      
      
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
          <Provider store={<!-- -->store}>
              <App />
          </Provider>
      );
      
      export default root;
      

3. Multiple reducers realize data sharing

3.1 Introduction to combineReducers() function

  • As your application becomes more complex, consider splitting the reducer function into multiple separate functions, with each function responsible for independently managing a portion of the state.

    The combineReducers auxiliary function is to merge an object with multiple different reducer functions as value into a final reducer function, and then call the createStore method on this reducer.

    The merged reducer can call each sub-reducer and merge their returned results into a state object. The state object returned by combineReducers() will name the state returned by each reducer passed in according to the corresponding key when it is passed to combineReducers().
    Example:

    rootReducer = combineReducers({<!-- -->potato: potatoReducer, tomato: tomatoReducer})
    // This will return the state object as follows
    {<!-- -->
      potato: {<!-- -->
        // ... potatoes, and some other state objects managed by potatoReducer ...
      },
      tomato: {<!-- -->
        // ... tomatoes, and some other state objects managed by tomatoReducer, such as the sauce attribute ...
      }
    }
    

  • The introduction to the combineReducers() function comes from the official website. For more explanations about combineReducers(), you can go to the official website, as follows:
    https://cn.redux.js.org/api/combinereducers.

3.2 Example of multi-reducer integration

3.2.1 The effect you want to achieve

  • First design the rendering of three components, as follows:
  • The states in these three components are all managed by react-redux. We first implement this without data sharing, and then implement how to enable data sharing between components.

3.2.2 Project directory design structure

  • as follows:

3.2.3 Integrate three reducers (or split reducers)

  • Regarding the splitting of reducers, you can go to the official website. It was also mentioned when introducing the combineReducers() function above, so I won’t go into details here. You can also go to the official website to see the logic of splitting reducers. The address is as follows:
    Split Reducer logic.
  • The above cat, dog, and pet correspond to one reducer respectively, but only one reducer is needed when creating a store, so ultimately these three reducer functions need to be merged into a final reducer function when creating a store. use.
  • How to use combineReducers() in this project, you don’t need to know what the other three reducers look like, as long as all three of them are exposed, so I will directly introduce the combination here, as follows:

3.2.4 Three core files for each component

3.2.4.1 Brief introduction – taking cat as an example
  • For the sake of convenience, the constants are not extracted here, and the UI components and container components are integrated into one file. Therefore, the three module components seen above each correspond to three core files: action, reducer, and container component.
  • The following uses the cat component as an example to illustrate:
    • catAction + catReducer:
      The cat here only wants to change “Today’s most popular cat breeds”, so this is relatively simple. An action function is enough. Then if the action is designed well, the reducer can also be perfected, as follows:
    • CatContainer component, as follows:
3.2.4.2 About dog and petPark
  • For dog, just look at it directly, as follows:
    • dogAction + dogReducer:
    • DogContainer component, as follows:
  • The petPark is as follows:
    This one is relatively simple, because there is no change in design status, so there is no corresponding action. They are all preliminary values, as follows:

3.3 Implement data sharing

3.3.1 Implement data sharing

  • Now based on the above effect, it is very simple to realize data sharing. Adding two lines of code is the same as taking your own, as follows:
    • petPark accesses data from the other two components:
    • Cat accesses the petPark data in the same way. You can access it however you want, because it is not managed inside the component, but react-redux manages it. Whoever uses it can get it:

3.4 Attached core code

3.4.1 Two actions

  • catAction is as follows:

    function changeCatKindAction(newKind){<!-- -->
        return {<!-- -->
            type: 'CHANGE_CAT_KIND',
            kind: newKind
        }
    }
    
    export {<!-- -->changeCatKindAction}
    
  • dogAction is as follows:

    function addDogAction(dogObj){<!-- -->
        return {<!-- -->
            type:'ADD_DOG',
            dog:dogObj
        }
    }
    
    export {<!-- -->addDogAction}
    

3.4.2 Three reducers + one integrated reducer

  • The first three are as follows:

    const catKindInit = 'Puppet';
    
    function catReducer(state=catKindInit, action){<!-- -->
        switch (action.type) {<!-- -->
            case 'CHANGE_CAT_KIND':
                return action.kind;
            default:
                return state;
        }
    }
    
    export default catReducer;
    
    const dogInit = [];
    // const dogInit = [{dogName:'dog',dogAge:1}];
    
    function dogReducer(state = dogInit, action){<!-- -->
        const {<!-- -->type,dog} = action;
        switch (type) {<!-- -->
            case 'ADD_DOG':
                return [...state,dog];
            default:
                return state;
        }
    }
    
    export default dogReducer;
    
    const adminInit = {<!-- -->parkAdmin:'Susu',parkAdminPhone:'176XXXXX'};
    
    function PetParkReducer(state=adminInit, action){<!-- -->
        return state; //There is no action, the initial state does not need to be modified, return directly
    }
    
    export default PetParkReducer;
    
  • The final one is as follows:

    import catReducer from "./catsReducer";
    import dogReducer from "./dogReducer";
    import petsParkReducer from "./petsParkReducer";
    
    import {<!-- --> combineReducers } from "redux";
    
    /**
     * 1. The merged reducer can call each sub-reducer and merge the results returned by them into a state object.
     * 2. The state object returned by combineReducers(),
     * The state returned by each reducer passed in will be named according to the corresponding key when it is passed to combineReducers().
     */
    const rootReducer = combineReducers({<!-- -->
        petParkState: petsParkReducer,
        dogState: dogReducer,
        catState: catReducer,
    });
    
    export default rootReducer;
    

3.4.3 Three components

  • CatContainer.jsx is as follows:

    import {<!-- --> connect } from "react-redux";
    import {<!-- --> useRef } from "react";
    import {<!-- -->changeCatKindAction} from '../redux/actions/CatAction'
    
    //1. UI components
    function CatUI(props){<!-- -->
        const catKindNode = useRef();
    
        function chagePopularKind(){<!-- -->
            const newKind = catKindNode.current.value;
            props.changKind(newKind);
        }
    
        return(
            <div>
                <h1>I am the cat component</h1>
                The most popular kitten breeds today are: {<!-- -->props.popularCatKind} <br/><br/>
    
                <input type="text" ref={<!-- -->catKindNode} placeholder="Please enter the most popular one today"/> & amp;nbsp;
                <button onClick={<!-- -->chagePopularKind}>Modify the most popular kitten breed</button>
    
                <br />
                Today’s administrator is: {<!-- -->props.guanliyuan} <br/>
                Administrator: {<!-- -->props.guanliyuan2.parkAdmin}
            </div>
        )
    }
    
    //2. Container component
    
    function mapStateToProps(state) {<!-- -->
        return {<!-- -->
            popularCatKind: state.catState,
    
            guanliyuan: state.petParkState.parkAdmin, //You can directly access one of the properties
            guanliyuan2: state.petParkState, //You can also directly access the entire object
        }
    }
    
    const mapDispatchToProps = {<!-- -->
        changKind: changeCatKindAction
    }
    
    const CatContainer = connect(mapStateToProps,mapDispatchToProps)(CatUI);
    
    export default CatContainer;
    
  • DogContainer.jsx is as follows:

    import {<!-- --> useRef } from "react";
    import {<!-- --> connect } from "react-redux"
    import {<!-- --> addDogAction } from "../redux/actions/DogAction";
    
    //1. Define UI components
    function DogUI(props){<!-- -->
        // console.log(props);
        const dogList = props.dogListState;//Get dog list information
    
        const dogNameRef = useRef();
        const dogAgeRef = useRef();
    
        function addParkDog(){<!-- -->
            const dogName = dogNameRef.current.value;
            const dogAge = dogAgeRef.current.value;
            const dogObj = {<!-- -->dogName:dogName,dogAge:dogAge}
            props.addOneDog(dogObj);
        }
    
        return(
            <div>
                <h1>I am a dog component</h1> <br />
                1. Dog park address: {<!-- -->props.dogParkAdress} <br /><br />
                2. 
                Dog name: <input type="text" ref={<!-- -->dogNameRef} /> <br /> & amp;nbsp; & amp;nbsp; & amp;nbsp;
                Dog age: <input type="number" ref={<!-- -->dogAgeRef}/> & amp;nbsp;
                <button onClick={<!-- -->addParkDog}>Add dog</button> <br /><br />
                3. Dog list information:
                <ul>
                    {<!-- -->
                        dogList.map((dog,index)=>(
                            <li key={<!-- -->index}>{<!-- -->dog.dogName}---{<!-- -->dog.dogAge}</li>)
                        )
                    }
                </ul>
            </div>
            
        )
    }
    
    //2. Container component and export container component
    
    const mapStateToProps = (state)=>{<!-- -->
        /**
         * 1. What is returned is an object (the dog component manages the state of its own component)
         * 2. Syntax problem: When returning an object, surround it with a pair of (), otherwise a syntax error will be reported
         */
        return(
            {<!-- -->
                dogListState:state.dogState,
                dogParkAdress:'Beijing Haidian District'
            }
        )
    }
    
    const mapDispatchToProps = {<!-- -->
        addOneDog: addDogAction
    }
    
    const DogContainer = connect(mapStateToProps,mapDispatchToProps)(DogUI);
    
    export default DogContainer;
    
  • PetParkContainer.jsx is as follows:

    import {<!-- --> connect } from "react-redux";
    import {<!-- --> useState } from "react";
    
    //1. UI components
    function PetParkUI(props){<!-- -->
        console.log(props);
        const [closeFlag,setCloseFlag] = useState(false);
    
        console.log(closeFlag);
        return(
            <div>
                <h1>I am a PetPark component</h1>
                1. Administrator: {<!-- -->props.parkAdminInfo.parkAdmin} <br /> & amp;nbsp; & amp;nbsp;
                   Administrator phone number: {<!-- -->props.parkAdminInfo.parkAdminPhone} <br />
                2. The existing pets are: {<!-- -->JSON.stringify(props.petKinds)} <br />
                3. Whether the park is closed on rainy days: {<!-- -->closeFlag ? 'Yes' : 'No'} <br /><br />
    
                Today’s cat species king is: {<!-- -->props.catKindKing} <br /><br />
                How many dogs are there in the dog park today: {<!-- -->props.dogsNum}
            </div>
        )
    }
    
    //2.Container component
    const mapStateToProps = (state)=>({<!-- -->
        parkAdminInfo: state.petParkState,//This is managed by react-redux and can be shared
        petKinds: ['cat','dog'] ,//If this is used by its own component, you can use useState to put it on your own component.
    
        //The following is data sharing
        catKindKing: state.catState, //Get the state in the cat component directly
        dogsNum: state.dogState.length
    
    })
    
    //The two parameters of connect are optional and can be passed or not.
    const PetParkContainer = connect(mapStateToProps)(PetParkUI);
    
    export default PetParkContainer;
    

4. Attached items

  • Project directory
  • Download project
    Various ways of react (react18) component communication and various detailed examples (including react-redux + redux-toolkit).