Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a React-Based Chat Client With Redux Part 1: React and React DOM

DZone's Guide to

Building a React-Based Chat Client With Redux Part 1: React and React DOM

In Part 1, we go over React and the ReactDOM, showing how they can used to make your JavaScript code better. Read on to get started!

· Web Dev Zone ·
Free Resource

Learn how error monitoring with Sentry closes the gap between the product team and your customers. With Sentry, you can focus on what you do best: building and scaling software that makes your users’ lives better.

Just two users casually chatting, thanks to React and Redux!
Let's build a non-trivial app with React and then refactor it to use Redux!

Much of the advice you get regarding the addition of Redux to your React projects is to only do so once they reach a certain size, because of the extra complexity Redux adds. That's certainly fair. But it will leave you with a bit of technical debt (refactoring to be done later) that you wouldn't have if you just started out with React and Redux.

Consequently, I thought it might be nice to present an exercise where we do just that: build an app as simply as possible using React and ReactDOM alone (not even JSX since you need more dependencies and a build process to support that), and then refactor to use JSX and Redux.

In a previous post, I described my Node-based multi-server chat project, which had a rudimentary Javascript client built in. Though all of the system functionality could be tested with several instances of that client, it was lacking in a number of ways:

  • It only allowed you to connect with two predefined users (this one provides freeform username input, so any number can connect).

  • It didn't show scrollable message history, although you could see the latest message sent or received on one line.

  • It didn't show you a list of all other users connected to the server mesh, it just logged the user list to the console whenever the server updated the client.

  • It didn't alert you when your selected recipient had disconnected or reconnected.

  • It didn't support message threads (this client automatically shows the appropriate thread and selects the recipient after an incoming message is received).

And as minimal as it was, that client was right at the edge of complexity where a framework of some sort would be welcome. So, in this post, I'm going to build a more fully featured client to communicate with that same chat server.

The First Cut: React and ReactDOM

In the most minimalistic React project, you just pull in React and ReactDOM, then use React.createElement() to render your components (which extend React.Component). You can see the first version of the project on its 1.1.0 tag.

In that version of the project, the HTML template looks like this:

react-chat-client.html

<!DOCTYPE html>
<html>
<head>
    <title>React Chat Client</title>
    <script src="https://unpkg.com/react@16.4.1/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.4.1/umd/react-dom.development.js"></script>
    <script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/main.js"></script>
</body>
</html>

Note how it uses script tags to pull in its dependencies, React, ReactDOM, and Socket.io, from CDN URLs, and the main.js file that is the entry point to the app. This allows us to serve the app with a simple Express server like this:

client-server.js

// Serve the client
const express = require('express');
const app = express();
const path = require('path');
const port = 8080;

console.log(`Server for React instant message client started. http://localhost:${port}/`);
app.use(express.static('app'));

app.get('/', function(req, res) {
    console.log('New client request');
    res.sendFile(path.join(__dirname + '/react-chat-client.html'));
});

app.listen(port);

That sidesteps a lot of mental friction associated with React toolchain setup. No need for create-react-app, Babel, Webpack, etc.

Application Structure

In this first cut, the app code only consisted of four files, tucked away in one folder.

You've already seen two of them above, client-server.js and react-chat-client.html. The other two are:

  • main.js

  • client.js

Main.js

This file couldn't be simpler and just rendered the Client class as a DOM element replacing the 'app' element in the HTML template. Recall it was referred to in a script tag in the body of the template.

import { Client } from './Client.js'

function render() {
    ReactDOM.render(
        React.createElement(Client),
        document.getElementById('app')
    )
}
render();

Client.js

This one is monolithic, weighing in at exactly six-hundred lines. I won't duplicate it here, but it's worth taking a look at in the repo. Monolithic classes are hard to maintain and reason about, particularly if there is more than one person on the team.

This file defines all the protocols, styles as CSS-in-JS, and so forth in constants that all components in the file can see. It also defines all the components, and, without hoisting, they have to be defined before they are referred to, so the client component is defined last and all the others come before it, and so on, with nested components. This non-obvious ordering requirement is another reason monolithic files are bad.

React.createElement vs JSX

One thing I will point out here is how, even though it is possible to do React without JSX, your render methods look like this:

    render() {
        return createElement('div', {style: clientStyle},

            // User selector
            createElement(UserInput, {
                connected: this.state.connected,
                onChange: this.onUserChange
            }),

            // Port selector
            createElement(PortSelector, {
                connected: this.state.connected,
                onChange: this.onPortChange
            }),

            // Recipient selector
            createElement(RecipientSelector, {
                users: this.state.users,
                recipient: this.state.recipient,
                onChange: this.onRecipientChange
            }),

            // Outgoing message input and send button
            createElement(MessageTransport, {
                connected: this.state.connected,
                recipient: this.state.recipient,
                outgoingMessage: this.state.outgoingMessage,
                onChange: this.onMessageInputChange,
                onSend: this.onSendMessage
            }),

            // Message History
            createElement(MessageHistory, {
                user: this.state.user,
                messages: this.state.messages,
                connected: this.state.connected
            }),

            // Footer (status line / connection toggle)
            createElement('div', {style: footerStyle},

                // Status Line
                createElement(StatusLine, {
                    status: this.state.status,
                    isError: this.state.isError
                }),

                // Connect button
                createElement(ConnectButton, {
                    enabled: (this.state.port && this.state.user),
                    connected: this.state.connected,
                    handleClick: this.onToggleConnection
                })
            )
        )
    }

instead of this one, from version 1.1.0 where JSX was added:

   render() {
        return <div style={clientStyle}>

            <UserInput connected={this.state.connected} onChange={this.onUserChange}/>

            <PortSelector connected={this.state.connected} onChange={this.onPortChange}/>

            <RecipientSelector users={this.state.users}
                               recipient={this.state.recipient}
                               onChange={this.onRecipientChange}/>

            <MessageTransport connected={this.state.connected}
                              recipient={this.state.recipient}
                              outgoingMessage={this.state.outgoingMessage}
                              onChange={this.onMessageInputChange}
                              onSend={this.onSendMessage}/>

            <MessageHistory user={this.state.user}
                            messages={this.state.messages}
                            connected={this.state.connected}/>

            <Footer status={this.state.status}
                    isError={this.state.isError}
                    connectEnabled={(this.state.port && this.state.user)}
                    connected={this.state.connected}
                    handleToggle={this.onToggleConnection}/>

        </div>;
    }

NOTE: This is a bit of an improvement, but I assure you we're going to vastly improve on that render function by the end of Part 2.

Data Flow

The basic premise of data flow in this Redux-less version is that the top-level Client component initializes all the state in its constructor:

        this.state = {
            connected: false,
            status: 'Select a user and port.',
            isError: false,
            user: null,
            recipient: NO_RECIPIENT,
            outgoingMessage: '',
            messages: [],
            port: PORTS[0],
            users: []
        };

and then passes the bits of state and callbacks that modify it down to the child components as props, like so:

            // User input field
            createElement(UserInput, {
                connected: this.state.connected,
                onChange: this.onUserChange
            }),

In this case, the onUserChange callback being passed to the  ConnectButton instance looks like:

   // The user input field has changed
    onUserChange(user) {
        this.setState({user: user});
    }

So when the UserInput component's text input field changes, triggering a call to onUserChange, the Client component changes the state, causing React to re-render.

That's all easy enough, and it's nice that React takes care of re-rendering when state changes. But in the real world, this approach doesn't scale. Components need to go into their own files, JSX is really much easier for us to work with, and most teams like some variant of the Flux pattern for managing application wide state, because it's easier to modularize and extend as a project grows.

That's all for Part 1. Now, onward to Part 2, where we refactor to use Redux and bindings!

Here again, for reference, is the first cut of the project:

What’s the best way to boost the efficiency of your product team and ship with confidence? Check out this ebook to learn how Sentry's real-time error monitoring helps developers stay in their workflow to fix bugs before the user even knows there’s a problem.

Topics:
react ,redux ,refactoring ,tutorial ,web dev

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}