Redux explained as a private group conversation

Image source:Β Pixabay

Like it or hate it, redux has become defacto state management system for complex reactjs and reactnative projects. However it does takes a little time to figure out the basic concepts and generate boilerplate code before one can use redux effectively in your next project and the official documentation seems a little daunting for people just starting their way into front end development. So I am writing this article to explain redux concepts by taking an analogy of a random group chat conversation as todo examples are old school and boring.

The three main components which make up redux are store,actions and reducers.Suppose you want to share some funny meme with your friends online. First thing you will need is a group containing all your friends to share your meme with them.In redux world this online group of your friends is called a store. It is a group to which all your friends(components) have subscribed to and it acts as a central repository to save all your group chat history. In redux world a store is an immutable entity which essentially means that a new copy of store is created on each update i.e. if someone posts some joke in your group all the subscribers(components) will receive a new copy of store with the new message appended at the end of chat history. The advantage is that you can see and control the state of store from initial state to final one or in our case simply view your entire chat history whenever you feel nostalgic or have nothing else to doΒ πŸ˜‰

For messages to pop up in group chat we first need to take the action of opening chat,typing the message and hit post button. Similarly in redux world, actions are the ones that invoke change in state of store.Once we hit the post button there has to be some built-in mechanism that takes your input from action, lookup current state of chat history and update the conversation for all subscribers based on your posting action. This built-in mechanism is the reducer in redux world which is a function that takes the action parameters and current state of store as input and returns new state of the store.

Enough with the boring theory stuff, lets us now build our own simple chat application to understand the concepts better. Here is a demo of what we are actually going to build today:

Now that I have your attention lets start with development. First create a new react project and install redux components along with react-bootstrap for creating a simple grid and chat-template to allow posting of messages:

sudo npm install -g create-react-app
create-react-app chatapp
cd chatapp
npm install --save redux redux-logger react-redux react-dom react-bootstrap chat-template redux-persist redux-thunk

The entire directory structure for the src folder of app is as follows:

.
β”œβ”€β”€ actions
β”‚ β”œβ”€β”€ actionTypes.js
β”‚ └── index.js
β”œβ”€β”€ App.css
β”œβ”€β”€ App.js
β”œβ”€β”€ App.test.js
β”œβ”€β”€ components
β”‚ β”œβ”€β”€ global
β”‚ β”‚ └── Navbar.js
β”‚ └── screens
β”‚ β”œβ”€β”€ ChatInput.js
β”‚ β”œβ”€β”€ css
β”‚ β”‚ └── ChatInput.css
β”‚ └── GroupChat.js
β”œβ”€β”€ index.css
β”œβ”€β”€ index.js
β”œβ”€β”€ logo.svg
└── reducers
β”œβ”€β”€ chatOperations.js
└── index.js

Here components folder contains all reusable components like navbar in global folder and specific page level components in screens folder,actions folder contains action specific file while reducer specific files are saved in reducers folder. Let us first build basic ui for our web app before we dive into redux…

First let us create simple Navbar component in global folder within components:

import React, {Component} from 'react';
import {Navbar, NavItem, Nav} from 'react-bootstrap';
export default class Appbar extends Component {
render() {
return (




Simple Chat App





Group Chat




)
}
}

Next we create a simple ChatInput component to allow user to post messages in screens folder within components:

import React, { Component } from 'react';
import { Button } from 'react-bootstrap';
import './css/ChatInput.css';
export default class ChatInput extends Component {
constructor(props) {
super(props);
this.state = {
messageValue: ''
}
}
/**
* Set chat message value
* @param {*} event
*/
handleChatMessage(event) {
this.setState({ messageValue: event.target.value });
}
/**
* Function to send user messages to store
* @param {*} event
*/
handlePostClick(event){
console.log("Button clicked");
}
render() {
return (

User 1


type="text"
className="messageValueInput"
id="messageValueInput"
name="messageValue"
key="messageValueInput"
value={this.state.messageValue}
onChange={(event) => this.handleChatMessage(event)} />


this.handlePostClick(event)}>Post

)
}
}

Now we need to display messages as chat conversation in GroupChat component within screens folder in components:

/**
* @file GroupChat
* Component to display messages displayed by the user
*/
import React, { Component } from 'react';
import Conversation from 'chat-template/dist/Conversation';
export default class GroupChat extends Component {
constructor(props){
super(props);
let messages = [{
message:'How do I use this messaging app?',
from: 'right',
backColor: '#3d83fa',
textColor: "white",
avatar: 'https://www.seeklogo.net/wp-content/uploads/2015/09/google-plus-new-icon-logo.png',
duration: 2000,
inbound:true
}];
this.state={
messages:messages
}
}
render() {
return (

GroupChat Messages


)
}
}

Next integrate all this components in App.js component:

import React, { Component } from 'react';
import './App.css';
import { Grid, Row, Col } from 'react-bootstrap';
import ChatInput from './components/screens/ChatInput';
import GroupChat from './components/screens/GroupChat';
import Navbar from './components/global/Navbar'
class App extends Component {
render() {
return (













);
}
}
export default App;

Phew…now that we are done with initial ui setup let’s get our hands dirty in building redux components 😎 :

In our case I am going to define a single action which allows the user to post messages to store. First define action constant in actionTypes.js file within actions folder as follows:

/**
* @file actionTypes
* All action constants are defined here
*/
export const POST_MESSAGES = "POST_MESSAGES"

Next we will define our reducer viz chatOperations.js file in reducers folder:

/**
* @file questionsOperations
* File to update store state based on called actions
*/
import { POST_MESSAGES } from '../actions/actionTypes'
const initialState = [{
message: 'How do I use this messaging app?',
from: 'right',
backColor: '#3d83fa',
textColor: "white",
avatar: 'http://res.cloudinary.com/technoetics/image/upload/v1491538348/technoetics/profilepenguin.png',
duration: 2000,
inbound: true
}];
export default function chatOperations(state = initialState, action) {
switch (action.type) {
case POST_MESSAGES:
return [
...state,
{
message: action.message,
from: 'right',
backColor: "green",
textColor: "white",
avatar: action.avatar,
duration: 2000,
inbound: false
}
]
default:
return state
}
}

Whenever we will encounter post message action, we simply push a new message to state object and update state of store.

Next pass this reducer to combineReducers in index.js in reducers folder:

import { combineReducers } from 'redux'
import chatOperations from './chatOperations'
const rootReducer = combineReducers({
chatOperations
})
export default rootReducer

Now lets us define action functions which calls this post message reducers in index file in actions folder:

/**
* @file actionsindex
* All dispatch actions defined here
*/
import * as types from './actionTypes'
export const postMessage = (message,avatar) => ({type: types.POST_MESSAGES,message,avatar})

Next modify main index.js in src folder as follows:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import { compose,createStore, applyMiddleware } from 'redux';
import {persistStore, autoRehydrate} from 'redux-persist'
import { Provider } from 'react-redux';
import reducer from './reducers';
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
const middleware = [ thunk ];
// middleware.push(createLogger());
const store = createStore(reducer,compose(applyMiddleware(...middleware),autoRehydrate()))
// begin periodically persisting the store
// persistStore(store)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

We have set up a simple redux store, applied createlogger for getting log inputs and used redux persist to store object state in localStorage. You can uncomment line 14 and 18 to get them working. Next we have passed store state to App.js using provider.

Now modify App.js as follows:

import React, { Component, PropTypes} from 'react';
import './App.css';
import { Grid, Row, Col } from 'react-bootstrap';
import ChatInput from './components/screens/ChatInput';
import GroupChat from './components/screens/GroupChat';
import Navbar from './components/global/Navbar';
//Redux components
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as messageActions from './actions';
class App extends Component {
constructor(props){
super(props);
}
render() {
return (














);
}
}
function mapStateToProps(state, ownProps) {
return {
messageItems: state.chatOperations
};
}
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(messageActions, dispatch)
})
App.propTypes = {
messageItems: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

Here App.js recieves store state as prop values using function mapStateToProps and available actions as props using mapDispatchToProps. I have used different syntax for both this functions and you can use any one which seems comfortable to you. Next we have passed the messageItems and actions props from app.js to ChatInput and GroupChat components on lines 23,24 and 28.

Next update onClick handler function of chatInput.js component(handlePostClick) as follows:

handlePostClick(event){
this.props.actions.postMessage(this.state.messageValue,"http://res.cloudinary.com/technoetics/image/upload/v1491538348/technoetics/profilepenguin.png")
}

This line calls action to udpate state in redux store and our chatOperations reducer will get the message and profile pic url from the above action.

Now modify GroupChat.js file to listen for posted messages and update the messages states:

/**
* @file GroupChat
* Component to display messages displayed by the user
*/
import React, { Component } from 'react';
import Conversation from 'chat-template/dist/Conversation';
import './css/GroupChat.css'
export default class GroupChat extends Component {
constructor(props) {
super(props);
console.log("propsItems", this.props.messageItems);
this.state = {
messages: this.props.messageItems
}
}
componentWillReceiveProps(nextProps) {
// console.log("nextProps", nextProps);
this.setState({ messages: nextProps.messageItems })
}
render() {
return (




GroupChat Messages





)
}
}

We have modified GroupChat to get populate messages from store before first render in constructor method and update messages queue when post messages action is dispatched in componentWillReceiveProps method.

You will have to import bootstrap css files in index.html along give some further styling to components and if you face any issues then you can check out the source code on my github repo for reference here.

Bonus Tips:

  • If you want to develop similar app in react native then you can use reactnative-gifted-chat module and follow similar flow.
  • I have not added a backend connection anywhere in the app since the emphasis was on learning redux but if required you can handle all ajax requests to backend in the reducer and update state of store accordingly.

Connect Deeper:

In the next article I will probably try to cover backend integration of the above chat app using Apollo graphql client and graphql server in nodejs and real-time face detection and recognition using opencv,nodejs and reactjs. If you are interested in getting notified about future posts then you can follow our facebook page here: Technoetics

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.