Understanding Redux: The World’s Easiest Guide to Beginning Redux

Ohans Emmanuel
Jun 1, 2018 · 102 min read

Introduction

Gosh! Does the learning streak ever end?

The course was pretty good, but I still don’t think Redux was well explained to a beginner like me. It wasn’t explained that well.

My Approach to Teaching Redux

“A rising tide lifts All boats”

A Note on Redux’s Learning Curve

A Tweet on Redux's learining curve from Eric Elliot.
A Tweet on Redux’s learining curve from Eric Elliot.

What You will Learn

A basic Hello World Redux application.
A basic Hello World Redux application.
Sample exercise apps we will work on together.
Skypey: The Skype clone we will build together.
Gif by Jona Dinges
Gif by Jona Dinges

Prerequisite

Download PDF & Epub for Offline Reading

Chapter 1 : Getting to know Redux

What is Redux?

What is Redux? As seen on the Redux Documentation
What is Redux? As seen on the Redux Documentation

Redux is a predictable state container for JavaScript apps.

It helps you write applications that behave consistently…

Why use Redux?

Explaining Redux to a 5 year Old

Young Joe heads to the bank.
Young Joe heads to the bank.
Young Joe heads to the bank with the intention to withdraw some money.
Young Joe heads to the bank with the intention to withdraw some money.
Young joe is at the bank! He goes straight to see the Cashier and makes his request known.
Young joe is at the bank! He goes straight to see the Cashier and makes his request known.
If only Young Joe got into the Vault. He'll cart away with as much as he finds.
If only Young Joe got into the Vault. He’ll cart away with as much as he finds.
Here's how you get money. Not from the Vault, sorry.
Here’s how you get money. Not from the Vault, sorry.
The bank vault can be likened to the Redux Store!
The bank vault can be likened to the Redux Store!

Have a single source of truth: The state of your whole application is stored in an object tree within a single Redux store.

The first Redux Princple
The first Redux Princple

State is read-only:

The only way to change the state is to emit an action, an object describing what happened.

{ 
type: "WITHDRAW_MONEY",
amount: "$10,000"
}
The Second Redux Principle.
The Second Redux Principle.
The Cashier and Vault communication!
The Cashier and Vault communication!

To specify how the state tree is transformed by actions, you write pure reducers.

The Third Redux Principle
The Third Redux Principle

Chapter 2: Your First Redux Application

We learn by example and by direct experience because there are real limits to the adequacy of verbal instruction.

Malcom Gladwell

The basic Hello World App.
The basic Hello World App.

The Structure of the React Hello World Application

The basic Hello World App with the default state, "React"
The basic Hello World App with the default state, “React”
The basic Hello World App with the tech prop changed to "Redux"
The basic Hello World App with the tech prop changed to “Redux”
import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
state = {
tech : "React"
}
render() {
return <HelloWorld tech={this.state.tech}/>
}
}

export default App;
<HelloWorld tech={this.state.tech}/>

Revisiting your Knowledge of Redux

Redux is a predictable state container for JavaScript apps.

{
tech: "React"
}
import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
// the state object has been removed.
render() {
return <HelloWorld tech={this.state.tech}/>
}
}

export default App;

Creating a Redux Store

import { createStore } from "redux"; //an import from the redux library
const store = createStore(); // an incomplete solution - for now.

The Store and Reducer Relationship

You have how much you even want to withdraw?
You have how much you even want to withdraw?
The Cashier and Vault communication!
The Cashier and Vault in sync!
The reducer is a mandatory argument passed into "createStore"
The reducer is a mandatory argument passed into “createStore”

The Reducer

Reducers are the most important concept in Redux.

Reducers are the most important concept in Redux. A more experienced engr. may argue in favour of middlewares.
Reducers are the most important concept in Redux. A more experienced engr. may argue in favour of middlewares.
let arr = [1,2,3,4,5]
let sum = arr.reduce((x,y) => x + y)
console.log(sum) //15
let arr = [1,2,3,4,5]
let sum = arr.reduce((x,y) => x + y)
console.log(sum) //15
createStore(reducer)
function createStore(reducer) {
var state;
var listeners = []

function getState() {
return state
}

function subscribe(listener) {
listeners.push(listener)
return unsubscribe() {
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}

function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}

dispatch({})

return { dispatch, subscribe, getState }
}
import { createStore } from "redux";  
const store = createStore(reducer); //this has been updated to include the created reducer.

Getting back to the Refactoring Process

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

import { createStore } from "redux";
const store = createStore(reducer);

class App extends Component {
render() {
return <HelloWorld tech={this.state.tech}/>
}
}

export default App;
export default () => {
}
export default (state) => {
}
export default (state) => {
return state
}

The Second createStore Argument

How much?
How much?
Uh, I need a new account with a $500 initial deposit
Uh, I need a new account with a $500 initial deposit
const store = createStore(reducer, initialState);
const initialState = { tech: "React " };
const store = createStore(reducer, initialState);
import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import reducer from "./reducers";
import { createStore } from "redux";

const initialState = { tech: "React " };
const store = createStore(reducer, initialState);

class App extends Component {
render() {
return <HelloWorld tech={this.state.tech}/>
}
}

export default App;
export default state  => {
return state
}
import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import { createStore } from "redux";

const initialState = { tech: "React " };
const store = createStore(reducer, initialState);

class App extends Component {
render() {
return <HelloWorld tech={store.getState().tech}/>
}
}
Replace "this.state" with "store.getState()"
Replace “this.state” with “store.getState()”
export default state => {
return state
}

Possible Gotcha

class App extends Component {
state = store.getState();
render() {
return <HelloWorld tech={this.state.tech} />;
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
}
class App extends Component {
state = {}
}

Conclusion and Summary

Introducing Exercises

Exercise

The exercise: User card app built with React. Refactor to use Redux.
The exercise: User card app built with React. Refactor to use Redux.

Chapter 3 : Understanding State Updates with Actions

A basic Hello World Redux application.
Updated design of the Hello world app.
The GIF!

What is a Redux Action?

{
type: "withdraw_money"
}
{
type: "withdraw_money",
amount: "$4000"
}
{
type: " ",
payload: {}
}
{
type: "withdraw_money",
payload: {
amount: "$4000"
}
}

Handling Responses to Actions in the Reducer

function reducer(state, action) {
//return new state
}
function reducer (state, action) {
switch (action.type) {
case "withdraw_money":
//do something
break;
case "deposit-money":
//do something
break;
default:
return state;
}
}
{
isOpen: true,
isClicked: false,
}
this.setState({isOpen: !this.state.isOpen})
this.setState({isClicked: !this.state.isClicked})
{
type: "is_open"
}
{
type: "is_clicked"
}
function reducer (state, action) {
switch (action.type) {
case "is_open":
return; //return new state
case "is_clicked":
return; //return new state
default:
return state;
}
}

Examining the Actions in the Application

{
type: "SET_TECHNOLOGY",
text: "React"
}
{
type: "SET_TECHNOLOGY",
text: "React-redux"
}
{
type: "SET_TECHNOLOGY",
text: "Elm"
}

Introducing Action Creators

export function setTechnology (text) {
return {
type: "SET_TECHNOLOGY",
tech: text
}
}
const setTechnology = text => ({ type: "SET_TECHNOLOGY", text });

Bringing Everything Together

import { createStore } from "redux";
import reducer from "../reducers";

const initialState = { tech: "React " };
export const store = createStore(reducer, initialState);
import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import ButtonGroup from "./ButtonGroup";
import { store } from "./store";

class App extends Component {
render() {
return [
<HelloWorld key={1} tech={store.getState().tech} />,
<ButtonGroup key={2} technologies={["React", "Elm", "React-redux"]} />
];
}
}

export default App;
import React from "react";

const ButtonGroup = ({ technologies }) => (
<div>
{technologies.map((tech, i) => (
<button
data-tech={tech}
key={`btn-${i}`}
className="hello-btn"
>
{tech}
</button>
))}
</div>
);

export default ButtonGroup;
<button 
data-tech="React"
key="btn-1"
className="hello-btn"> React </button>
<div>
{technologies.map((tech, i) => (
<button
data-tech={tech}
key={`btn-${i}`}
className="hello-btn"
onClick={dispatchBtnAction}
>
{tech}
</button>
))}
</div>
{
type: "SET_TECHNOLOGY",
tech: "React"
}
{
type: "SET_TECHNOLOGY",
tech: "React-redux"
}
function dispatchBtnAction(e) {
const tech = e.target.dataset.tech;
store.dispatch(setTechnology(tech));
}
function setTechnology (text) {
return {
type: "SET_TECHNOLOGY",
text: text
}
}

Actions Dispatched. Does this Thing Work?

export default (state, action) => {
console.log(action);
return state;
};
{type: "@@redux/INITu.r.5.b.c"}

Making the Reducer Count

const initialState = { tech: "React" };
export const store = createStore(reducer, initialState);
{
type: "SET_TECHNOLOGY",
text: "React-Redux"
}
export default (state, action) => {
switch (action.type) {
case "SET_TECHNOLOGY":
//do something.

default:
return state;
}
};
export default (state, action) => {
switch (action.type) {
case "SET_TECHNOLOGY":
return {
...state,
tech: action.text
};

default:
return state;
}
};

Never Mutate State Within the Reducers

export default (state, action) => {
switch (action.type) {
case "SET_TECHNOLOGY":
state.tech = action.text;
return state;

default:
return state;
}
};
return {
...state,
tech: action.text
};

Subscribing to Store Updates

ReactDOM.render(<App />, document.getElementById("root")
const render = function() {
ReactDOM.render(<App />, document.getElementById("root")
}
const render = function() {
ReactDOM.render(<App />, document.getElementById("root")
}
render()
const render = () => ReactDOM.render(<App />, document.getElementById("root"));

render();
store.subscribe(render);
class App extends Component {
render() {
return [
<HelloWorld key={1} tech={store.getState().tech} />,
<ButtonGroup key={2} technologies={["React", "Elm", "React-redux"]} />
];
}
}

Important Note on Using store.subscribe()

The new Counter Vanilla example is aimed to dispel the myth that Redux requires Webpack, React, hot reloading, sagas, action creators, constants, Babel, npm, CSS modules, decorators, fluent Latin, an Egghead subscription, a PhD, or an Exceeds Expectations O.W.L. level.

Okay, Are We Done Yet?

Conclusion and Summary

Exercises

{
name: "Ohans Emmanuel",
balance: 1559.30
}
{
days: 11,
hours: 31,
minutes: 27,
seconds: 11,
activeSession: "minutes"
}

Chapter 4: Building Skypey: A More Advanced Example.

Planning the Application

The Skypey application

Resolving the Initial App Layout

create-react-app Skypey
const App = () => {
return (
<div className="App">
<Sidebar />
<Main />
</div>
);
};
#root {
height: 100vh;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.App {
height: 100%;
display: flex;
color: rgba(189, 189, 192, 1);
}
import React from "react";
import "./Sidebar.css";

const Sidebar = () => {
return <aside className="Sidebar">Sidebar</aside>;
};

export default Sidebar;
.Sidebar {
width: 80px;
background-color: rgba(32, 32, 35, 1);
height: 100%;
border-right: 1px solid rgba(189, 189, 192, 0.1);
transition: width 0.3s;
}

/* not small devices */
@media (min-width: 576px) {
.Sidebar {
width: 320px;
}
}
import React from "react";
import "./Main.css";

const Main = () => {
return <main className="Main">Main Stuff</main>;
};

export default Main;
.Main {
flex: 1 1 0;
}
.Main {
flex: 1 1 0;
background-color: rgba(25, 25, 27, 1);
height: 100%;
}
The humble result with the Sidebar and Main sections laid out.
The humble result with the Sidebar and Main sections laid out.

Designing the State object

The multiple contacts a user may have.
The multiple contacts a user may have.
Clicking a contact displays their message in the Main pane.
Clicking a contact displays their message in the Main pane.
Hmmm....A giant user box with nested contacts
Hmmm….A giant user box with nested contacts
A giant user array with nested contacts
A giant user array with nested contacts
const state = {
user: [
{
contact1: 'Alex',
messages: [
'msg1',
'msg2',
'msg3'
]
},
{
contact2: 'john',
messages: [
'msg1',
'msg2',
'msg3'
]
}
]
const state = {
user: [],
messages: [
'msg1',
'msg2'
],
contacts: ['Contact1', 'Contact2']
}
const state = {
user: [],
messages: [
{
messageTo: 'contact1',
text: "Hello"
},
{
messageTo: 'contact2',
text: "Hey!"
}
],
contacts: ['Contact1', 'Contact2']
}
user:  {
name,
email,
profile_pic,
status:,
user_id
}
A visual display of some of user's properties.
A visual display of some of user’s properties.
contacts: [
{
name,
email,
profile_pic,
status,
user_id
},
{
name,
email,
profile_pic,
status,
user_id_2
}
]
contacts: {
user_id: {
name,
email,
profile_pic,
status,
user_id
},
user_id_2: {
name,
email,
profile_pic,
status,
user_id_2
}
}
messages: [
{
messageTo: 'contact1',
text: "Hello"
},
{
messageTo: 'contact2',
text: "Hey!"
}
]
{
text,
is_user_msg
};
Note how some messages are psotioned on the left, and others on the right.
Note how some messages are positioned on the left, and others on the right.
{
text: "Hello there. U good?",
is_user_msg: false
}
messages: {
user_id: {
text,
is_user_msg
},
user_id_2: {
text,
is_user_msg
}
}
messages: {
user_id: {
0: {
text,
is_user_msg
},
1: {
text,
is_user_msg
}
},
user_id_2: {
0: {
text,
is_user_msg
}
}
}

The Major Problem with Using Objects Over Arrays

Caveat #1 : It’s a lot easier to iterate over Arrays in your view logic

const users = this.props.users; 

users.map(user => {
return <User />
})

Solution #1a:

//import the library
import _ from "lodash"

//use it
_.map(users, (user) => {
return <User />
})

Solution #1b:

const users = this.props.users;

users.map(user => {
return <User />
})
const users = this.props.users; //this is an object. 

_.values(users).map(user => {
return <User />
})
{
user_id_1: {user_1_object},
user_id_2 {user_2_object},
user_id_3: {user_3_object},
user_id_4: {user_4_object},
}
[
{user_1_object},
{user_2_object},
{user_3_object},
{user_4_object},
]

Caveat #2 : Preservation of Order

const numbers = [0,3,1,6,89,5,7,9]
const numbers = {
0: "Zero",
3: "Three",
1: "One",
6: "Six",
89: "Eighty-nine",
5: "Five",
7: "Seven",
9: "Nine"
}
The problem with objects.
The problem with objects.
The problem with objects & unpreserved order.
The problem with objects & unpreserved order.

Solution #2:

const numbers = {
0: "Zero",
3: "Three",
1: "One",
6: "Six",
89: "Eighty-nine",
5: "Five",
7: "Seven",
9: "Nine"
}
numbersOrderIDs: [0, 3, 1, 6, 89, 5, 7, 9]

Recap on the Design of the State Object

Example state object for 2 user contacts. in the application we'll build, there'll be 10 contacts with 10 initial messages.
Example state object for 2 user contacts. In the application we’ll build, there’ll be 10 contacts with 10 initial messages.

Building the List of Users

We should have something like this when we successfully build out the list of users.
We should have something like this when we successfully build out the list of users.
contacts.map(contact => <User />)

Setting up the Store

yarn add redux
import { createStore } from "redux";

const store = createStore(someReducer, initialState);

export default store;
const store = createStore(reducer, {contacts});
import reducer from "../reducers";
import { contacts } from "../static-data";
export default (state, action) => {
return state;
};
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";


ReactDOM.render(<App />, document.getElementById("root"));
registerServiceWorker();
const render = () => {
return ReactDOM.render(<App />, document.getElementById("root"));
};
render()
import store from "./store";
store.subscribe(render);
const render = () => {
fancyLog();
return ReactDOM.render(<App />, document.getElementById("root"));
};
function fancyLog() {
console.log("%c Rendered with 👉 👉👇", "background: purple; color: #FFF");
console.log(store.getState());
}
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import store from "./store";

const render = () => {
fancyLog();
return ReactDOM.render(<App />, document.getElementById("root"));
};

render();
store.subscribe(render);
registerServiceWorker();

function fancyLog() {
console.log("%c Rendered with 👉 👉👇", "background: purple; color: #fff");
console.log(store.getState());
}
Our basic logger at work.
Our basic logger at work.

Passing the Sidebar data via Props

const App = () => {
const { contacts } = store.getState();

return (
<div className="App">
<Sidebar contacts={contacts} />
<Main />
</div>
);
};
The contacts object successfully passed as props to Sidebar
The contacts object successfully passed as props to Sidebar
yarn add lodash
import  _ from lodash
<Sidebar contacts={_.values(contacts)} />
Note that the contacts props is now an array.
Note that the contacts props is now an array.
import React from "react";
import User from "./User";
import "./Sidebar.css";

const Sidebar = ({ contacts }) => {
return (
<aside className="Sidebar">
{contacts.map(contact => <User user={contact} key={contact.user_id} />)}
</aside>
);
};

export default Sidebar;

Building the User Component

import React from "react";
import "./User.css";

const User = ({ user }) => {
const { name, profile_pic, status } = user;

return (
<div className="User">
<img src={profile_pic} alt={name} className="User__pic" />
<div className="User__details">
<p className="User__details-name">{name}</p>
<p className="User__details-status">{status}</p>
</div>
</div>
);
};

export default User;
The sidebar rendered with unstyled contacts.
The sidebar rendered with unstyled contacts.
.Sidebar {
...
overflow-y: scroll;
}
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700");

body {
...
font-weight: 400;
font-family: "Nunito Sans", sans-serif;
}
.User {
display: flex;
align-items: flex-start;
padding: 1rem;
}
.User:hover {
background: rgba(0, 0, 0, 0.2);
cursor: pointer;
}
.User__pic {
width: 50px;
border-radius: 50%;
}
.User__details {
display: none;
}

/* not small devices */
@media (min-width: 576px) {
.User__details {
display: block;
padding: 0 0 0 1rem;
}
.User__details-name {
margin: 0;
color: rgba(255, 255, 255, 0.8);
font-size: 1rem;
}
}
The sidebar contacts well styled!
The sidebar contacts well styled!

Got questions?

You don’t have to Pass Down Props

An high level structure of the Skypey layout
An high level structure of the Skypey layout
Pass props from App, as done in normal React apps.
Pass props from App, as done in normal React apps.
With Redux, you dont have to pass props. Just get the required values directly from the store
With Redux, you dont have to pass props. Just get the required values directly from the store

Container and Component Folder Structure

import Sidebar from "../components/Sidebar";
import Main from "../components/Main";
import Sidebar from "./Sidebar";
import Main from "./Main";

Refactoring to Set Initial State from the Reducer

const store = createStore(reducer, { contacts });
import { createStore } from "redux";
import reducer from "../reducers";

const store = createStore(reducer);

export default store;
export default (state, action) => {
return state;
};
import { contacts } from "../static-data";

export default (state = { contacts }, action) => {
return state;
};

Reducer Composition

{ 
tech: "React"
}
Example state object for 2 user contacts. in the application we'll build, there'll be 10 contacts with 10 initial messages.
export const function messagesReducer (state={}, action) {
return state
}

Refactoring Skypey to Use Multiple Reducers

import { contacts } from "../static-data";

export default (state = contacts, action) => {
return state;
};
import { generateUser } from "../static-data";
export default function user(state = generateUser(), action) {
return state;
}
import user from "./user";
import contacts from "./contacts";
import { combineReducers } from "redux";
export default combineReducers({
user,
contacts,
});
export default combineReducers({
user: user,
contacts: contacts
})

I’m Lost. How does this work again?

const state = {
user: "me",
messages: "hello",
contacts: ["no one", "khalid"],
activeUserId: 1234
}
const state = {
user: getUser(),
messages: getMsg(),
contacts: getContacts(),
activeUserId: getID()
}
const state = {
user: user(),
messages: messages(),
contacts: contacts(),
activeUserId: activeUserId()
}
const state = killerFunction({
user: user,
messages: messages,
contacts: contacts,
activeUserId: activeUserId
})
const state = killerFunction({
user,
messages,
contacts,
activeUserId
})
import { combineReducers } from "redux";
import user from "./user";
import contacts from "./contacts";

export default combineReducers({
user,
contacts
});
"user" and "contacts" now exist in the state object.
“user” and “contacts” now exist in the state object.

Building the Empty Screen

import React from "react";
import "./Main.css";

const Main = () => {
return <main className="Main">Main Stuff</main>;
};

export default Main;
export default function activeUserId(state = null, action) {
return state;
}
...
import activeUserId from "./activeUserId";

export default combineReducers({
user,
contacts,
activeUserId
});
const { contacts, user, activeUserId  } = store.getState();
const { contacts } = store.getState();
<Main user={user} activeUserId={activeUserId} />
<Main  />
import React from "react";
import "./Main.css";

const Main = () => {
return <main className="Main">Main Stuff</main>;
};

export default Main;
import React from "react";
import "./Main.css";
import Empty from "../components/Empty";
import ChatWindow from "../components/ChatWindow";

const Main = ({ user, activeUserId }) => {
const renderMainContent = () => {
if (!activeUserId) {
return <Empty user={user} activeUserId={activeUserId} />;
} else {
return <ChatWindow activeUserId={activeUserId} />;
}
};
return <main className="Main">{renderMainContent()}</main>;
};

export default Main;
import React from "react";
import "./Empty.css";

const Empty = ({ user }) => {
const { name, profile_pic, status } = user;
const first_name = name.split(" ")[0];

return (
<div className="Empty">
<h1 className="Empty__name">Welcome, {first_name} </h1>
<img src={profile_pic} alt={name} className="Empty__img" />
<p className="Empty__status">
<b>Status:</b> {status}
</p>
<button className="Empty__btn">Start a conversation</button>
<p className="Empty__info">
Search for someone to start chatting with or go to Contacts to see who
is available
</p>
</div>
);
};

export default Empty;
{ 
name,
email,
profile_pic,
status,
user_id:
}
const { name, profile_pic, status } = user;
const first_name = name.split(" ")[0];
import React from "react";

const ChatWindow = ({ activeUserId }) => {
return (
<div className="ChatWindow">Conversation for user id: {activeUserId}</div>
);
};

export default ChatWindow;
The ustyled Empty screen being displayed.
The ustyled Empty screen being displayed.
.Empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.Empty__name {
color: #fff;
}
.Empty__status,
.Empty__info {
padding: 1rem;
}
.Empty__status {
color: rgba(255, 255, 255, 0.9);
border-bottom: 1px solid rgba(255, 255, 255, 0.7);
}
.Empty__img {
border-radius: 50%;
margin: 2rem 0;
}
.Empty__btn {
padding: 1rem;
margin: 1rem 0;
font-weight: bold;
font-size: 1.2rem;
border-radius: 30px;
outline: 0;
}
.Empty__btn:hover {
background: rgba(255, 255, 255, 0.7);
cursor: pointer;
}
With some CSS, the Empty screen quickly comes to life.
With some CSS, the Empty screen quickly comes to life.
Looking good? The result so far!
Looking good? The result so far!

Building the Chat Window

Here's the entire chat window.
Here’s the entire chat window.
{
type: "SET_ACTION_ID",
payload: user_id
}
export const SET_ACTIVE_USER_ID = "SET_ACTIVE_USER_ID";
switch(action.type)  {
case "WITHDRAW_MONEY":
doSomething();
break;
}
export const seWithdrawAmount = amount => ({
type: "WITHDRAW_MONEY,
payload: amount
})
import { SET_ACTIVE_USER_ID} from "../constants/action-types";

export const setActiveUserId = id => ({
type: SET_ACTIVE_USER_ID,
payload: id
});
<div className="User">
<div className="User" onClick={handleUserClick.bind(null, user)}>
function handleUserClick({ user_id }) {
store.dispatch(setActiveUserId(user_id));
}
import { setActiveUserId } from "../actions";
import React from "react";
import "./User.css";
import store from "../store";
import { setActiveUserId } from "../actions";

const User = ({ user }) => {
const { name, profile_pic, status } = user;

return (
<div className="User" onClick={handleUserClick.bind(null, user)}>
<img src={profile_pic} alt={name} className="User__pic" />
<div className="User__details">
<p className="User__details-name">{name}</p>
<p className="User__details-status">{status}</p>
</div>
</div>
);
};

function handleUserClick({ user_id }) {
store.dispatch(setActiveUserId(user_id));
}

export default User;
function handleUserClick({ user_id }) {
console.log(user_id);
store.dispatch(setActiveUserId(user_id));
}
export default function activeUserId(state = null, action) {
return state;
}
import { SET_ACTIVE_USER_ID } from "../constants/action-types";export default function activeUserId(state = null, action) {
switch (action.type) {
case SET_ACTIVE_USER_ID:
return action.payload;
default:
return state;
}
}
Hurray! We've got this working for now ;)
Hurray! We’ve got this working for now ;)

Breaking the ChatWindow into smaller components

Here's the entire chat window.
Here’s the entire chat window.
The sub components, Header, Chats and MesageInput
The sub components, Header, Chats and MesageInput
import React from "react";

const ChatWindow = ({ activeUserId }) => {
return (
<div className="ChatWindow">Conversation for user id: {activeUserId}</div>
);
};

export default ChatWindow;
import React from "react";
import store from "../store";
import Header from "../components/Header";

const ChatWindow = ({ activeUserId }) => {
const state = store.getState();
const activeUser = state.contacts[activeUserId];

return (
<div className="ChatWindow">
<Header user={activeUser} />
</div>
);
};

export default ChatWindow;
The information in the header are those of the clicked contact.
The information in the header are those of the clicked contact.
import React from "react";
import "./Header.css";

function Header({ user }) {
const { name, status } = user;
return (
<header className="Header">
<h1 className="Header__name">{name}</h1>
<p className="Header__status">{status}</p>
</header>
);
}

export default Header;
.Header {
padding: 1rem 2rem;
border-bottom: 1px solid rgba(189, 189, 192, 0.2);
}
.Header__name {
color: #fff;
}
Looking good!
Looking good!
The Chats component to be built
The Chats component to be built
import { getMessages } from "../static-data";

export default function messages(state = getMessages(10), action) {
return state;
}
import messages from "./messages";

export default combineReducers({
user,
messages,
contacts,
activeUserId
});
Messages now exist in the state object.
Messages now exist in the state object.
... 
import Chats from "../components/Chats";
...
return (
<div className="ChatWindow">
<Header user={activeUser} />
<Chats />
</div>
);
... 
import Chats from "../components/Chats";
...
const activeMsgs = state.messages[activeUserId];

return (
<div className="ChatWindow">
<Header user={activeUser} />
<Chats messages={activeMsgs} />
</div>
);
Have a look at the messages field one more time
Have a look at the messages field one more time
... 
import _ from "lodash";
import Chats from "../components/Chats";
...
const activeMsgs = state.messages[activeUserId];

return (
<div className="ChatWindow">
<Header user={activeUser} />
<Chats messages={_.values(activeMsgs)} />
</div>
);
import React, { Component } from "react";
import "./Chats.css";

const Chat = ({ message }) => {
const { text, is_user_msg } = message;
return (
<span className={`Chat ${is_user_msg ? "is-user-msg" : ""}`}>{text}</span>
);
};

class Chats extends Component {
render() {
return (
<div className="Chats">
{this.props.messages.map(message => (
<Chat message={message} key={message.number} />
))}
</div>
);
}
}

export default Chats;
const Chat = ({ message }) => {
const { text, is_user_msg } = message;
return (
<span className={`Chat ${is_user_msg ? "is-user-msg" : ""}`}>{text}</span>
);
};
<span> {text} </span>
<span className="Chat  is-user-msg"> {text} </span>
<span className="Chat"> {text} </span>
.Chat.is-user-msg {

}
<span className={`Chat ${is_user_msg ? "is-user-msg" : ""}`}>{text}</span>
The current result of our iterations. Still needs some work.
The current result of our iterations. Still needs some work.
.ChatWindow {
display: flex;
flex-direction: column;
height: 100vh;
}
ChatWindow - not the best of looks yet.
ChatWindow — not the best of looks yet.
.Chats {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: flex-start;
width: 85%;
margin: 0 auto;
overflow-y: scroll;
}
.Chat {
margin: 1rem 0;
color: #fff;
padding: 1rem;
background: linear-gradient(90deg, #1986d8, #7b9cc2);
max-width: 90%;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.Chat.is-user-msg {
margin-left: auto;
background: #2b2c33;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}

@media (min-width: 576px) {
.Chat {
max-width: 60%;
}
}
How beautiful is that :)
How beautiful is that :)
.Chat.is-user-msg {
background: #2b2c33;
}
export const SET_TYPING_VALUE = "SET_TYPING_VALUE";
{
type: SET_TYPING_VALUE,
payload: "input value"
}
import {
SET_ACTIVE_USER_ID,
SET_TYPING_VALUE
} from "../constants/action-types";


export const setTypingValue = value => ({
type: SET_TYPING_VALUE,
payload: value
})
import { SET_TYPING_VALUE } from "../constants/action-types";

export default function typing(state = "", action) {
switch (action.type) {
case SET_TYPING_VALUE:
return action.payload;
default:
return state;
}
}
handle the SET_TYPING_VALUE type
handle the SET_TYPING_VALUE type
As a default, return the same state
As a default, return the same state
...
import typing from "./typing";

export default combineReducers({
user,
messages,
typing,
contacts,
activeUserId
});
import React from "react";
import store from "../store";
import { setTypingValue } from "../actions";
import "./MessageInput.css";

const MessageInput = ({ value }) => {

const handleChange = e => {
store.dispatch(setTypingValue(e.target.value));
};

return (
<form className="Message">
<input
className="Message__input"
onChange={handleChange}
value={value}
placeholder="write a message"
/>
</form>
);
};

export default MessageInput;
...
import MessageInput from "./MessageInput";
const { typing } = state;

return (
<div className="ChatWindow">
<Header user={activeUser} />
<Chats messages={_.values(activeMsgs)} />
<MessageInput value={typing} />
</div>
);
.Message {
width: 80%;
margin: 1rem auto;
}
.Message__input {
width: 100%;
padding: 1rem;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border: 0;
border-radius: 10px;
font-size: 1rem;
outline: 0;
}
The results so far!
The results so far!

Submitting the Form

...
<form className="Message" onSubmit={handleSubmit}>
...
</form>
...
{
type: "SEND_MESSAGE",
payload: {
message,
userId
}
}
//first retrieve the current state object
const state = store.getState();

const handleSubmit = e => {
e.preventDefault();
const { typing, activeUserId } = state;
store.dispatch(sendMessage(typing, activeUserId));
};
import {
...
SEND_MESSAGE
} from "../constants/action-types";

export const sendMessage = (message, userId) => ({
type: SEND_MESSAGE,
payload: {
message,
userId
}
})
export const SEND_MESSAGE = "SEND_MESSAGE";
import { setTypingValue, sendMessage } from "../actions";

Updating the Message State

activeUserId.js
contacts.js
messages.js
typing.js
user.js
import { getMessages } from "../static-data";

export default function messages(state = getMessages(10), action) {
return state;
}
import { getMessages } from "../static-data";
import { SEND_MESSAGE } from "../constants/action-types";

export default function messages(state = getMessages(10), action) {
switch (action.type) {
case SEND_MESSAGE:
return "";
default:
return state;
}
}
an overview of what's to be done.
an overview of what’s to be done.
{ 
number: 10,
text: "the text typed",
is_user_msg: true
}
switch (action.type) {
case SEND_MESSAGE:
const { message, userId } = action.payload;
const allUserMsgs = state[userId];
const number = +_.keys(allUserMsgs).pop() + 1;

return {
...state,
[userId]: {
...allUserMsgs,
[number]: {
number,
text: message,
is_user_msg: true
}
}
};

default:
return state;
}
const {message, userId } = action.payload
const allUserMsgs = state[userId];
{
0: {
number: 0,
text: "first message"
is_user_msg: false
},
1: {
number: 0,
text: "first message"
is_user_msg: false
}
}
[ 0, 1, 2, 3, 4, 5]
const number = +_.keys(allUserMsgs).pop() + 1;
return {
...state,
[userId]: {
...allUserMsgs,
[number]: {
number,
text: message,
is_user_msg: true
}
}
};
[number]: {
number,
text: message,
is_user_msg: true
}
have another look at the return statement. Can you make sense of this now?
have another look at the return statement. Can you make sense of this now?

Tweaks to Make the Chat Experience Natural

DEMO so far.
DEMO so far.
case SEND_MESSAGE:
return "";
import { SET_TYPING_VALUE, SEND_MESSAGE } from "../constants/action-types";

export default function typing(state = "", action) {
switch (action.type) {
case SET_TYPING_VALUE:
return action.payload;
case SEND_MESSAGE:
return "";
default:
return state;
}
}
constructor(props) {
super(props);
this.chatsRef = React.createRef();
}
<div className="Chats" ref={this.chatsRef}>
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
scrollToBottom = () => {
this.chatsRef.current.scrollTop = this.chatsRef.current.scrollHeight;
};
...

class Chats extends Component {
constructor(props) {
super(props);
this.chatsRef = React.createRef();
}
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
scrollToBottom = () => {
this.chatsRef.current.scrollTop = this.chatsRef.current.scrollHeight;
};

render() {
return (
<div className="Chats" ref={this.chatsRef}>
{this.props.messages.map(message => (
<Chat message={message} key={message.number} />
))}
</div>
);
}
}

export default Chats;
Final results looking all good! Gorgeous!
Final results looking all good! Gorgeous!

Conclusion and Summary

Exercises

Include the edit message functionality shown here.
Include the delete message functionality shown here.

Chapter 5: What Next ?

The sequel to this current book
The sequel to this current book

freeCodeCamp.org

This is no longer updated. Go to https://freecodecamp.org/news instead

Ohans Emmanuel

Written by

I build complex frontends. I teach intricate subjects. https://leanpub.com/reintroducing-react

freeCodeCamp.org

This is no longer updated. Go to https://freecodecamp.org/news instead