Integrating Redux and react

Posted on Aug - 2018

by krishna singh

Integrating Redux and redux-thunk inside react application.

This post is about using redux inside react application with an example.

You can refer my post on quick guide to react and understanding redux as a standalone library since this post will be using many concepts from there.

learnwebtechs.com/2018/08/06/quick-guide-to-react-with-example

learnwebtechs.com/2018/08/12/understand-redux-basics-with-example

Let's get started with creating a new react application using create-react-app an NPM global package used to create standard react based project template and manage workflow.

 

create-react-app react-redux-async-thunk .

 

The above command will create project react-redux-async-thunk inside the current folder.

You can clone the demo project from the GitHub link mentioned below. This will also be used to explain the next post on redux-thunk and handling of async operation inside react application.

https://github.com/krishna28/react-redux-async-thunk/tree/simple-react-app

The above branch uses only react to add and delete messages. The only modified file is App.js with the code mentioned below.

App.js

import React, { Component } from 'react';
import './App.css';

class App extends Component {

  constructor(props){
    super(props);
    this.state = {
      username:"",
      message:"",
      messageList: []
    }
  }

  onUsernameChange = (event) => {
   event.preventDefault();
   let username = event.target.value;
   this.setState(() => {
     return {
       username: username
     }
   })
  } 

  onMessageChange = (event) => {
    event.preventDefault();
    let message = event.target.value;
    this.setState(() => {
      return {
        message: message
      }
    })
  }


  remove = (id) => {
    const updatedList = this.state.messageList.filter((message) => message.id !== id);
    this.setState(() => {
      return {
        messageList: updatedList
      }
    })
  }

  
  onSubmit = (event) => {
    event.preventDefault();
    let message = this.state.message;
    let username = this.state.username;
    this.setState((prevState) =>{
      return {
        messageList: prevState.messageList.concat({message:message,username:username,id:Date.now()})
      }
    })
  }

  render() {
    return (
      <div className="wrapper">
        <form name="post" onSubmit={this.onSubmit}>
        <input style={{padding:'10px',margin:'10px',width:'70%',textAlign:'left'}}
         type="text" 
         placeholder="username" 
         name="username" value={this.state.username}
         onChange={this.onUsernameChange} /><br />

        <textarea style={{padding:'10px',margin:'10px',width:'70%',rows:'50',cols:'40',textAlign:'left'}} 
        placeholder="message"
        name="message" 
        value={this.state.message}
        onChange={this.onMessageChange}>
         </textarea><br />

         <input style={{padding:'10px',margin:'10px',backgroundColor:'orange',textAlign:'left'}}
          type="submit"
           value="Post" />
        </form>
        <div>
          {this.state.messageList.map((message) => {
            return <div style={{display:'inline-block',
            padding:'20px',cursor:'pointer', margin:'0 20px 20px 0',
             border:'1px solid black', width:'200px'}} key={message.id}
              onClick={() => this.remove(message.id)}>
              <p>Username: {message.username}</p>
              <p>Message: {message.message}</p>
            </div>
          })}
        </div>
      </div>
    );
  }
}

export default App;

 

Let's go through the step by step process of integrating redux using the above example.

Step 1. Install redux through NPM.

npm install redux --save

 

Step 2. As redux is a standalone library we need a way to connect redux with react. The package used to connect react with redux is react-redux.

npm install react-redux --save

 

 Step 3. Let update the index.js mentioned below and go through it.

Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';

import './index.css';
import App from './App';

const actions = {
  ADD:"ADD",
  REMOVE:"REMOVE"
}
  
  let initialState = {
    messageList:[]
  }

  
  let rootReducer = (state = initialState,action) => {
  
    switch(action.type){
      case actions.ADD:
       return {...state,
         messageList:state.messageList.concat(action.payload)
        }
      case actions.REMOVE:    
       return {...state, 
        messageList: state.messageList.filter(item => item.id !== action.payload)
      }
    }
    return state;
  
  }

let store = createStore(rootReducer);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));

 

#3 imports a special component Provider from react-redux which is used to connect react root component App with redux using the store as a property and passing it the store created from redux at #35.

#36 wraps the root component App inside the Provider. A provider has a dependency on the store, which is created using createStore function imported from redux at #4 and initialized at #35.

The createStore function accepts the first argument as a root reducer which is marked as required and rest arguments as optional which are used to enhance the store like middlewares, initial state.

#19  defined as reducer used inside the createStore. #14 defines the initial state of the application and is used as part of the redux store.

#9 defines actions which describe the intent of the operation.

Step 4. Let's update the App.js  and go through its changes.

App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';


import './App.css';

const actions = {
  ADD:"ADD",
  REMOVE:"REMOVE"
}


let addMessage = (username,message,dispatch) => {

    dispatch({
      type:actions.ADD,
      payload: {
        message:message,
        username:username,
        id:Date.now()
      }
     })
}


let removeMessage = (id,dispatch) => {
  dispatch({
    type:actions.REMOVE,
    payload:id
  })
}


class App extends Component {

  constructor(props){
    super(props);
    this.state = {
      username:"",
      message:""
    }
  }

  onUsernameChange = (event) => {
   event.preventDefault();
   let username = event.target.value;
   this.setState(() => {
     return {
       username: username
     }
   })
  } 

  onMessageChange = (event) => {
    event.preventDefault();
    let message = event.target.value;
    this.setState(() => {
      return {
        message: message
      }
    })
  }

  
  onSubmit = (event) => {
    event.preventDefault();
    let message = this.state.message;
    let username = this.state.username;
    this.props.add(message,username);

  }

  render() {
    return (
      <div className="wrapper">
        <form name="post" onSubmit={this.onSubmit}>
        <input style={{padding:'10px',margin:'10px',width:'70%',textAlign:'left'}}
         type="text" 
         placeholder="username" 
         name="username" value={this.state.username}
         onChange={this.onUsernameChange} /><br />

        <textarea style={{padding:'10px',margin:'10px',width:'70%',rows:'50',cols:'40',textAlign:'left'}} 
        placeholder="message"
        name="message" 
        value={this.state.message}
        onChange={this.onMessageChange}>
         </textarea><br />

         <input style={{padding:'10px',margin:'10px',backgroundColor:'orange',textAlign:'left'}}
          type="submit"
           value="Post" />
        </form>
        <div>
          {this.props.messages.map((message) => {
            return <div style={{display:'inline-block',
            padding:'20px',cursor:'pointer', margin:'0 20px 20px 0',
             border:'1px solid black', width:'200px'}} key={message.id}
              onClick={() => this.props.remove(message.id)}>
              <p>Username: {message.username}</p>
              <p>Message: {message.message}</p>
            </div>
          })}
        </div>
      </div>
    );
  }
}
//returns an object with keys as properties made available to props e.g this.props.messages
const mapStateToProps = state => {
  return {
    messages: state.messageList
  }
}
//return an object with keys as properties made available to props e.g this.props.add 
//and this.props.remove 
const mapDispatchToProps = dispatch => {
return {
 add : (username,message) => addMessage(username,message,dispatch),
 remove: (id) => removeMessage(id,dispatch) 
}
}

export default connect(mapStateToProps,mapDispatchToProps)(App);

 

#2 imports a function named connect from the react-redux package which is used to add or attach state and action creators to props of the component.

Note:  The most important thing to understand is that redux is used to manage the state of an application using predictable and standard exposed functions like actions, action creators, and reducers.  As we know components being the building blocks of a react applications which are driven by the application state and event handler functions, connect helps to bind action creators and state to props which can drive the component. Props are an immutable form of state used inside child components and helping react to follow a unidirectional flow of data

 

#13 and #26  defines action creators which are just helper functions used to return actions with the type and payload.

#110 defines a special function used to bind slice of the redux store to props of the component. This function has access to state of the redux which is made available to it though connect function. This is passed as the first argument to connect method.

#117 defined a special function used to bind action creators(in simple terms they are similar to event handler functions which on certain action try to update the state of an application) to props of the component so that it can be used to update the state based on certain events. It has access to special function dispatch used to fire actions that can be consumed by the reducer based on the type of the action. This is passed as the second argument to connect function.

#123 wraps the App component using the connect function which returns a HOC(Higher Order Function).HOC will be explained in a different blog post, in short, it's a component which takes another component as an argument and enriches it with some data.

The App component also has local state username and message used to control UI of the application and is an example of a controlled component. 

Github URL for the specific changes as per branch.

https://github.com/krishna28/react-redux-async-thunk/tree/integrating-react-redux

Hope this post helped to clear the concept of integrating redux with react. Stay connected for next post on redux-thunk and handling of async operation inside react application.

Thanks