React Redux Concepts - Part Two

This article explains how to set up our solutions when we want to learn a step further in React, which is communication between components. I have created two demos performing the same functionality, but both have used different concepts - one uses plain React and the other one uses Redux. For passing data between components, we can use any of the approaches as per our requirement or according to our project’s complexity. We can either use Plain React or Redux for State Management.

I have discussed when we should use plain React and when Redux needs to come in the picture. Are you having the basic knowledge of React and want to learn some state management tools like Redux? Yes, this article will help you understand the basic Redux concept as this article compares the plain React concepts and basic Redux concepts.

Redux is a state management tool for any JavaScript application. We need to use React Redux as we are using Redux with React, so we need to bind our React components with Redux so that our components can read data from the Redux store, and actions can be dispatched for updating the data in the store.

For setting up Redux in your environment, you need to install some packages, you can run the following commands to install them.
  1. npm install --save redux  
  2. npm install react-redux  
  3. npm i redux-thunk  
I have created two demo projects performing the same functionalities, but both have used different concepts - one uses plain React and the other one uses Redux. You can refer to the below links which avoid the overhead to set up the environment.

Demo using Redux and Demo using plain React

Let’s get started by setting up communication between React components using both - plain React, and Redux.

Setting up communication between components

 
Plain React

For creating a simple project without using Redux, we need to maintain the parent and child relationship between the components. We need to pass the components in a parent component, for this demo we have parent component as Post component which consists of child components that are “PostForm” and “PostList”.

Parent component (Post)
  1. import React, { Component } from 'react';   
  2.    export interface IState {   
  3.    ...   
  4.    }  
  5.    export default class Posts extends Component <{}, IState> {   
  6.    ...   
  7.       render() {   
  8.          return (   
  9.             <>   
  10.                <div>   
  11.                   <PostForm .../>   
  12.                   <hr />   
  13.                   <PostList.../>   
  14.                </div>   
  15.             </>   
  16.          )   
  17.       }   
  18. }   
We will use props to pass data from the parent (Post) to any of the child components. And to pass data from child component to parent component we use callback(s) and states.

With Redux 

We don’t need to maintain the parent and child relationship when we are using Redux as a state management tool rather than using state and props for communication.

We need to create a store that maintains the application level state and configure it so that our application components can use it.

Configuring Redux store
  1. import React, { Component } from 'react';   
  2.   
  3. import configureStore from './store';   
  4.   
  5. import { Provider } from 'react-redux'   
  6. class App extends Component {   
  7.    render() {   
  8.       return (   
  9.          <Provider store={configureStore()}>   
  10.             <div className="App">   
  11.              ...   
  12.             </div>   
  13.          </Provider>   
  14.       );   
  15.    }   
  16. }   
  17. export default App;   
Provider

The Provider component gets the state as props so that each child component can implicitly access the managed state from the store, which is wrapped inside the connect().

The best practice is to wrap the Top-level component inside the provider so that the entire Application’s tree resides in it and we can use the connected component.

ConfigureStore( ) does the following tasks,
  • Initializes our state
  • Adds middleware I.e.“Thunk”
  • Adds enhancers I.e. Redux dev tools
  • Creates store.
  1. ...   
  2. export default function configureStore() {   
  3.    const initialState = {};   
  4.    const middlewares = [thunk];   
  5.    const middlewareEnhancer = applyMiddleware(...middlewares);   
  6.    const enhancers = [middlewareEnhancer];   
  7.    const composedEnhancer = composeWithDevTools(...enhancers);   
  8.    const store = createStore(   
  9.       rootReducer,   
  10.       initialState,   
  11.       composedEnhancer   
  12.    );   
  13.    return store;   
  14. }   
In simple words, applyMiddleware() is a Redux extension, it is used for supporting async actions. With a plain basic Redux store, you can only do simple synchronous updates by dispatching an action. Middleware extends the store's abilities and lets you write async logic that interacts with the store.

createStore()

CreateStore is a function that returns an object “store” and it is used to create the state tree of our complete application.

Syntax
  1. createStore(reducer, [preloadedState], [enhancer])     
Reducer: It is a function that returns the state tree. It may contain the root reducer of our application which may consist of multiple reducers combined using the combineReducer as per requirements.

PreloadedState: It is the initial state of our application I.e. empty object as per our demo.

Enhancer: It is a function that is used for adding store enhancers to our application. We have used Redux dev tools and middleware.

Passing data between components

 
Plain React

Passing data in plain React uses the concept of props. Props are used to pass the data to the child component, which may consist of the data or callback functions.

As shown below, our child component “PostForm” has 4 props which consist of state of the parent component and the callback functions for updating the state which simultaneously updates the UI.

We maintain the state in the component as we need our child components to know that the state is updated. If we would have maintained the states in each child component, then it wouldn’t be possible to update the UI of one component on the update of the sibling component’s state.

Taking the example of our demo, we need to show the submitted details from the “PostFrom” component to the “PostList” component as soon as the submit button is clicked. This would not be possible if we wouldn’t have maintained the relationship between the components.
  1. ...   
  2. export default class Posts extends Component<{}, IState> {   
  3.    constructor(props: any, state: IState) {   
  4.       super(props);   
  5.       this.state = {   
  6.          allPosts: [] as any[],   
  7.          editPost: {},   
  8.          isFormEditing: false   
  9.       }  
  10.    }   
  11.    createPost = (postData: any) => {   
  12.    ...   
  13.    }   
  14.    …   
  15.    render() {   
  16.       return (   
  17.          <>   
  18.             <div>   
  19.                <PostForm   
  20.                   onCreatePost={this.createPost}   
  21.                   formValues={this.state.editPost}   
  22.                   isFormEditing={this.state.isFormEditing}   
  23.                   updatePost={this.updatePost}  
  24.                />   
  25.                <hr />   
  26.                <PostList   
  27.                   allPosts={this.state.allPosts}   
  28.                   getPost={this.getPost}   
  29.                />   
  30.             </div>   
  31.          </>   
  32.       )   
  33.    }   
  34. }   
Using Redux

For passing the data to the component using Redux, the component needs to be wrapped in the connect( ) function. If we don’t want our component to re-render on updating the application’s state, then we don’t need the component to be connected to the Redux store.

We can also manually subscribe to the store using the store.subscribe() which will subscribe to the changes in the store. However, it is better to use connect rather than using subscribe as it is more optimized, and performance is improved which we might be able to implement when using subscribe.

Connect creates a Higher order Component which wraps the React-based component creating container component.  
  1. class Posts extends Component<any, IState> {   
  2.    ...   
  3. }   
  4. export default connect(mapStateToProps, { fetchPosts, getPost })(Posts);   
Connect()

Syntax
  1. connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)     
In our demo, we have mapStateToProps defined which return an object which contains the state objects. Instead of mapDispatchToProps, we have directly destructured the functions. To get started we don’t need to worry about the other two parameters.

mergeProps defines how the final props for your own wrapped component are determined. If you do not provide mergeProps, your wrapped component receives { ...ownProps, ...stateProps, ...dispatchProps }by default.

So, now we will get our component “Posts” wrapped inside a component that injects props into our component.
 

Accessing the data

 
Plain React

The data passed to child components will be received by them in the form of props. In the child component, we can call the method of the parent component as shown, we call the onCreatePost() method of the parent component with the parameters to pass. We can access the state of the parent component as we have used this.props.formValuesin our child component which has the data from the parent component.
  1. this.props.onCreatePost(post);   
  2. …   
  3. this.props.formValues;   
Using Redux

Using Redux allows us to use the state of the store which can be updated only by dispatching an action. When we connect our component to the Redux store, the actions and the state of the store are received as props to our component.

One thing we need to keep in mind is that when passing the interface for props, we need to pass any, as we will receive the props from the store. 
 
The action “fetchPosts” is used to call the action which will dispatch the data to the reducer for updating the state of the store. Which will in return notify all the connected components with the updated state tree.

this.props.fetchposts will call this action which will in return dispatch object with type and the data. Data here we passed as payload.
  1. ...   
  2. export const fetchPosts = () => (dispatch: any) => {   
  3.    ...   
  4.    dispatch({   
  5.       type: FETCH_POSTS,   
  6.       payload: response.data   
  7.    }));   
  8. }   
  9. ...   
Type: FETCH_POST will search for type in reducer and update the state tree accordingly.
  1. ...   
  2. export default function (state = initialState, action: any) {   
  3.    switch (action.type) {   
  4.       case FETCH_POSTS:   
  5.          return {   
  6.             ...state,   
  7.             items: action.payload,   
  8.             operation: action.type   
  9.          }   
  10.    }   
  11. }   
  12. ...   

Summary


The usage of state management tools requires you to make decisions based on the complexity of your project. As the project grows, we may require some state management tool but that too depends on the complexity. If it makes it difficult for you to understand where your state has been stored and which function is called when then it is the right time to switch to Redux. Yes, getting started with Redux seems difficult but believe me, it is easy after you are familiar with the flow and concepts of Redux.