Getting Reactive on the Horizon with your Webpack: <ReChat Tutorial(part 2) />

<Part 1 />

This tutorial’s source code can be found here:

Since last we left off, we have a literally barebones application that allows a users to land on our local host page provided by Horizon.js. Enter in a name, and a message. And then add that message to our RethinkDB on a button click.

Before we go further on in part 2, I highly suggest dropping your DB collection and starting fresh. We will be adjusting our message contents, and it may muss up their order if you don’t. Horizon doesn’t currently support this in a traditional way, so to work around that, I went and found a CLI for Rethink. This will make your life a little easier and it will help you inspect your collections that Horizon sets up for you.

RethinkDB Command Line Tool:

ReQL reference:

To drop the old collection, you must open a connection to RethinkDB:

rethinkdb

Then in another terminal tab, start your recli repl:

recli

Inside the repl, run:

r.dbDrop(‘rechat’)
r.dbDrop(‘rechat_internal’)

Your db is now empty again!


Now that we’re at this point, we can start to flesh out the rest of the application. We know certain things we have to improve. The most glaring of these is the ordering of our messages. (Admittedly, this is something I overlooked in the first tutorial[my bad!], but I was just really excited to get it out). Refactoring of our components will be something we do as well. We have a small application at the moment, so this will be the best time to do it. Our lower level components are going to have their logic extracted and we will end up doing all of our ‘work’ in app.jsx. Finally, for our own gratification, we’re gonna add some styling!

Let’s start by updating our package.json:

{
“name”: “rechat”,
“version”: “1.0.0”,
“description”: “”,
“main”: “index.js”,
“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1"
},
“author”: “”,
“license”: “ISC”,
“dependencies”: {
@horizon/client”: “^1.0.3”,
“babel-core”: “^6.9.1”,
“babel-loader”: “^6.2.4”,
“babel-plugin-transform-runtime”: “^6.9.0”,
“babel-polyfill”: “^6.9.1”,
“babel-preset-es2015”: “^6.9.0”,
“babel-preset-react”: “^6.5.0”,
“babel-preset-stage-0”: “^6.5.0”,
“moment”: “^2.13.0”,
“react”: “^15.1.0”,
“react-dom”: “^15.1.0”,
“react-redux”: “^4.4.5”,
“redux”: “^3.5.2”,
“webpack”: “^1.13.1”
}
}

You can copy paste this over and run npm install to update your /node_modules folder. (*NOTE I have Redux dependencies included, but will not use them in this part of the tutorial)

Next, create a Bootswatch file:

touch dist/bootswatch.css

I’m using the source code found here: https://bootswatch.com/cosmo/bootstrap.css (copy and paste this into our new css file) and include it into our index.html like so:

<!doctype html>
<html>
<head>
<meta charset=”UTF-8">
<link rel=”stylesheet” href=”./bootswatch.css”></link>
<link rel=”stylesheet” href=”./style.css”></link>
</head>
<body>
<div class=’attach’></div>
</body>
<script src=’./bundle.js’></script>
</html>

Run your start up commands for the application:

webpack --watch --progress --colors
hz serve --dev

And open localhost:8181, At this point our application looks like this(Cosmo makes it pretty):

localhost:8181/

Ordering our messages

can’t have that now, can we?

This is actually quite easy. Right now, to go get our messages from the DB, our file looks like this:

messages.jsx

import React, { Component } from ‘react’;
import Message from ‘./message’;
class Messages extends Component {
constructor(props) {
super(props);
this.chat = props.chat;
//init our Messages state with and empty conversation array
this.state = {
convo: []
};
}
//when our component mounts, it will call to the database with
//this.chat.watch, setting the state and re-rendering our component
//with our messages
componentDidMount() {
this.chat.watch().subscribe(
(messages) => {
let convo = messages.map(function(message) {
return message
});
this.setState({convo: convo});
},
(err) => {
console.log(err);
}
);
}
//and our mappping function that used to render our dummy data, 
//now outputs our db messages
render() {
let msgsjsx = this.state.convo.map(function(message, i){
return <Message msg={message} key={i} />
});
return (
<div className=’container-fluid’>
{msgsjsx}
</div>
);
}
}
export default Messages;

To get our messages ordering correctly, our good old fashioned Array.prototype.sort() comes to the rescue! We’re going to refactor our componentDidMount to look like this:

componentDidMount() {
this.chat.watch().subscribe(
(messages) => {
let allMSGS = messages.sort(function(a, b){
//this will return an array where the messages are sorted in
//descending order by time
return b.date — a.date;
})
this.setState({convo: allMSGS});
},
(err) => {
console.log(err);
}
);
}

Notice that inside our sorting function, we are using b.date -a.date. This is a property we are going to need to add to our message object, in order to accomplish the sort.

Inside of app.jsx, we have a sendMessage in our class object that looks like this:

sendMessage() {
if(this.state.text === false || this.state.author === false) {
alert(‘Invalid Submission’);
return;
}
let message = {
text: this.state.text,
author: this.state.author
};
chat.store(message);
}

Alter this method so the message object being created also has a date property:

sendMessage() {
if(this.state.text === ‘’ || this.state.author === ‘’) {
alert(‘Invalid Submission’);
return;
}
let now = Date.now();
let message = {
text: this.state.text,
author: this.state.author,
date: now
}
chat.store(message);
}

What this does, is it creates a Unix timestamp. It is the number of milliseconds that have elapsed since January 1, 1970. These numbers will allow our sort function to work in the way we wrote in. We’re almost done fixing our messaging display, but if we were to add messages and run localhost:8181 we would see this:

thats cool, but it’s not

Almost there! As we can see it’s sorting our timestamps, but only really smart humans can actually understand it. To transform this, we’re going to use a module from our updated package.json: Moment.js.

Update your message.jsx:

import React, { Component } from ‘react’;
import Moment from ‘moment’;
export default class Message extends Component {
constructor(props) {
super(props);
this.props = props;
}
render() {
return (<div className=’row’>
<div className=’col-xs-2 center’>
{this.props.msg.author}
</div>
<div className=’col-xs-3 center’>
{ Moment(this.props.msg.date)
.format(‘MMMM Do YYYY, h:mm:ss a’)}
</div>
<div className=’col-xs-7 center’>
{this.props.msg.text}
</div>
</div>);
}
}

Open up localhost:8181 again, and it should look like this:

BAM!

THIS MIGHT BE A GOOD TIME TO TAKE A BREAK AND GIT COMMIT


Refactoring components

With React, one of the best practices involved in writing components, is to have the parent ‘container’ components taking care of logic, and lower level children components being constrained to simply describing the way the application looks. To start refactoring, we need to look at actions being taken by our components at the moment.

message.jsx is set up correctly, it is receiving props and rendering. This is a good example of a dumb component(no logic).

messages.jsx is something we need to fix. Although it works as we intended, we are creating a new state object local to the component and it’s children components. We only want to have one source of truth to our application, so that means we need to have one state.

Change messages.jsx to this:

import React, { Component } from ‘react’;
import Message from ‘./message’;
export default class Messages extends Component {
constructor(props) {
super(props);
this.props = props;
}
  render() {
let msgsjsx = this.props.convo.map(function(message, i){
return <Message msg={message} key={i} />
});
return (
<div className=’container-fluid’>
{msgsjsx}
</div>
);
}
}

In this new version of our messages.jsx, we have removed the this.state and the componentDidMount(). These changes will be addressed in the app.jsx component. If you take a closer look, you’ll notice that our Messages is a dumb component now, that gives us a container for our individual Message components.

Lets start our app.jsx refactor by moving our button and our input fields.

mkdir src/chat
touch src/chat/chatContainer.jsx

chatContainer.jsx

import React, { Component } from ‘react’;
export default class ChatContainer extends Component {
constructor(props) {
super(props);
this.sendMessage = props.sendMessage;
this.handleChangeAuthor = props.handleChangeAuthor;
this.handleChangeText = props.handleChangeText;
}
 render() {
return (
<form>
<div className=’center’>
<button onClick={this.sendMessage}>Send
Message</button>
<input onChange={this.handleChangeAuthor}
placeholder=’Name’></input>
<input onChange={this.handleChangeText}
placeholder=’Message’></input>
</div>
</form>
)
}
}

We are passing in our methods as props, allowing all our logic to remain in our parent component. But, basically it’s the same bits of component that we extracted from app.jsx. (The this binding is bound in our app.jsx, no need to worry about that here)

We can also move our messages components into their own file for modularity:

mkdir src/msgs

Now let’s take a look at what our final app.jsx looks like:

import React, { Component } from ‘react’;
import Messages from ‘./msgs/messages’;
import ChatContainer from ‘./chat/chatContainer’;
const Horizon = require(‘@horizon/client’);
const horizon = Horizon({ secure: false });
const chat = horizon(‘messages’);
class App extends Component {
constructor(props) {
super(props);
this.state = {
convo: [],
author: ‘’,
text: ‘’
}
}
  componentDidMount() {
chat.watch().subscribe(
(messages) => {
let allMSGS = messages.sort(function(a, b){
return b.date — a.date;
})
this.setState({convo: allMSGS});
},
(err) => {
console.log(err);
}
);
}
  handleChangeAuthor(event) {
this.setState({author: event.target.value});
}
  handleChangeText(event) {
this.setState({text: event.target.value});
}
  sendMessage() {
if(this.state.text === ‘’ || this.state.author === ‘’) {
alert(‘Invalid Submission’);
return;
}
let now = Date.now();
let message = {
text: this.state.text,
author: this.state.author,
date: now
}
chat.store(message);
}
  render() {
return (
<div>
<ChatContainer
handleChangeAuthor={this.handleChangeAuthor.bind(this)}
handleChangeText={this.handleChangeText.bind(this)}
sendMessage={this.sendMessage.bind(this)}
/>
<Messages convo={this.state.convo}/>
</div>
);
}
}
export default App;

What happened?

  • We included our ChatContainer
  • Refactored our app.jsx state object to represent the initial state of our application
  • We moved the componentDidMount() from src/msgs/messages.jsx and kept it exactly the same(nice and easy!)
  • Bound this to our methods that we are passing to ChatContainer as props
  • Passed our this.state.convo to our Messages component to delegate rendering of messages

Revisit localhost:8181/ and everything should be looking just the way we left it.

(If you have any issues, the source code can be found at the reChat github repository under the tutorialpt2 branch)

THIS MIGHT BE A GOOD TIME TO TAKE A BREAK AND GIT COMMIT


STYLING

(…sure you are)

No reason to spend too much time on this since styling is so subjective, and my love of blue may not be shared by all…

…but to give you an idea of some simple styling that can be completed in about 15 minutes with the wonderful classes of Bootswatch….

not too bad…

Check out the source code for this at (tutorialpt2 branch):

Hope you had fun with this part… Looking at adding in some Oauth in the next part!

-Mick

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.