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!
Join the DZone community and get the full member experience.
Join For Freemuch 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:
-
react and reactdom only ( version 1.0.0 )
Published at DZone with permission of Cliff Hall, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
How Agile Works at Tesla [Video]
-
Best Practices for Securing Infrastructure as Code (Iac) In the DevOps SDLC
-
Top Six React Development Tools
-
Knowing and Valuing Apache Kafka’s ISR (In-Sync Replicas)
Comments