Written by Akash Mittal ✏️
The web was originally implemented on the principle of request and response: a client sends a request and a server replies with an appropriate response. When there is no requirement for data manipulation according to a client request, it’s customary to serve a static page.
When dynamic pages came along, we were introduced to the concept of GET, POST, PUT, and DELETE requests. Now a client is able to ask for custom data from a server by sending its requirements as parameters. The server then processes the request and returns a dynamic response.
Still, the fundamentals are based on request and response: the client requests, the server responds, and the connection is closed.
There is another protocol that doesn’t work like a request-response scheme known as WebSockets. In this tutorial, we’ll introduce you to the WebSockets technology and some of its common use cases. We will walk you through how to implement WebSockets in React Native by designing a message broadcast app.
What are WebSockets?
WebSockets is a protocol that provides full-duplex communication, which means the client and server stay connected over a single TCP connection. Unlike request-response communication, the connection doesn’t close here.
Since it’s full-duplex, the server can also send data to the client without a request. This helps in saving bandwidth and response time.
The WebSockets protocol starts with ws://
. For dealing with hypertext, we use http://
or https://
.
Why use WebSockets?
There is a range of applications for WebSockets, but we primarily use them in cases where data needs to be either broadcasted or pushed by server.
For example, a chat app server needs to send a message to a recipient as soon as the sender sends it. The server can’t wait for the client to request new messages, so it pushes them to the client using full-duplex communication. Similarly, news, trade matrix, and even social media posts are pushed in this way.
This outcome could be achieved before WebSockets entered the picture, but they were unreliable and inefficient.
For instance, many companies used long polling in which a browser sends a request but the server does not respond. The connection stays active until there is a connection timeout. In the meantime, if server has any message for the client, it responds and the connection closes. As soon as the connection closes, either due to timeout or server response, the client again sends the request. This process keeps going.
Another approach is to send an AJAX request in an interval of few seconds — say, three seconds — and get a server response, which is either a valid message or empty object. So, a new message can get delayed for, at most, three seconds. The downside of this approach is that a lot of API calls go to waste because the server doesn’t have any message to respond to.
How to use WebSockets in React Native
There are four main functions that are executed during the lifecycle of WebSockets. They are called when the app establishes a connection, receives a messages, catches an error, and disconnects.
Let’s zoom in on these functions.
Creating a WebSockets connection
The first step is to establish a connection with the server. WebSockets work on their own protocol, ws://
.
In React Native, we can create a connection using the following code:
var ws = new WebSocket('ws://host.com/path');
Here the link corresponds to the socket service running on the backend.
If the server accepts the connection, it notifies the client by invoking the onopen
function over the WebSocket object:
ws.onopen = () => {
// connection opened
ws.send('something'); // send a message
};
This acts like the constructor. You can allocate resources in this function. Also, if you need to send some one-time information, such as a user identification number, to the server, you can do so here.
Receiving messages
In a request-response policy, the client looks for a response in the sent request. This means the client knows when it will get data from the server so it stays ready to handle it. But in full-duplex communication like WebSockets, the server can send data at any time without the consent of the client.
To handle this situation, a function must be invoked when the server sends a message.
In React Native, we have an onmessage
function for this:
ws.onmessage = (e) => {
// a message was received
console.log(e.data);
};
Handling errors
When there is an error, either due to poor internet connectivity or internal server errors, the onerror
function is called:
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
Connection close
When the connection closes, either by the server or by the client, the onclose
function is called. This acts like a destructor in that you can release the allocated resources.
ws.onclose = (e) => {
// connection closed
console.log(e.code, e.reason);
};
Now we’ve seen the complete lifecycle of WebSockets in React Native, from establishing a connection to closing it. We can write all these functions in single block as follows:
var ws = new WebSocket('ws://host.com/path');
ws.onopen = () => {
// connection opened
ws.send('something'); // send a message
};
ws.onmessage = (e) => {
// a message was received
console.log(e.data);
};
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose = (e) => {
// connection closed
console.log(e.code, e.reason);
};
WebSockets example: Message broadcasting app
To show WebSockets in action, let’s create a simple message broadcasting app in Rect Native. In our demo app, a message sent from one application will be broadcast to all connected applications.
We’ll develop the server script in Node.js. Here's the WebSockets server code:
const express = require("express");
const app = express();
const http = require("http");
const WebSocket = require("ws");
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on("connection", function connection(ws) {
ws.on("message", function incoming(message, isBinary) {
console.log(message.toString(), isBinary);
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
});
app.get("/", (req, res) => {
res.send("Hello World!");
});
server.listen(8080, () => {
console.log("Listening to port 8080");
});
You can find this code in the npm repositoriy for the ws package.
Remember, you need to keep the server active. Otherwise, clients won’t be able to connect.
Client code
Now that our server is running successfully, it’s time to create our React Native app.
At the top, we’ll create a horizontal bar to show connection or disconnection notifications as well as errors. At the bottom, we’ll place an input field and a submit button to send messages through WebSockets. The rest of the middle area will be used to display the list of messages received from the server.
It’s a broadcast app, so any message sent from any device will be broadcasted to all.
Let’s check out the code:
import * as React from 'react';
import { Text, View, StyleSheet, TextInput, Button, ScrollView } from 'react-native';
export default function App() {
const [serverState, setServerState] = React.useState('Loading...');
const [messageText, setMessageText] = React.useState('');
const [disableButton, setDisableButton] = React.useState(true);
const [inputFieldEmpty, setInputFieldEmpty] = React.useState(true);
const [serverMessages, setServerMessages] = React.useState([]);
var ws = React.useRef(new WebSocket('ws://w567l.sse.codesandbox.io/')).current;
React.useEffect(() => {
const serverMessagesList = [];
ws.onopen = () => {
setServerState('Connected to the server')
setDisableButton(false);
};
ws.onclose = (e) => {
setServerState('Disconnected. Check internet or server.')
setDisableButton(true);
};
ws.onerror = (e) => {
setServerState(e.message);
};
ws.onmessage = (e) => {
serverMessagesList.push(e.data);
setServerMessages([...serverMessagesList])
};
}, [])
const submitMessage = () => {
ws.send(messageText);
setMessageText('')
setInputFieldEmpty(true)
}
return (
<View style={styles.container}>
<View style={{
height: 30,
backgroundColor: '#eeceff',
padding: 5
}}>
<Text>{serverState}</Text>
</View>
<View style={{
backgroundColor: '#ffeece',
padding: 5,
flexGrow: 1
}}>
<ScrollView>
{
serverMessages.map((item, ind) => {
return (
<Text key={ind}>{item}</Text>
)
})
}
</ScrollView>
</View>
<View style={{
flexDirection: 'row',
}}>
<TextInput style={{
borderWidth: 1,
borderColor: 'black',
flexGrow: 1,
padding: 5,
}}
placeholder={'Add Message'}
onChangeText={text => {
setMessageText(text)
setInputFieldEmpty(text.length > 0 ? false : true)
}}
value={messageText}
/>
<Button
onPress={submitMessage}
title={'Submit'}
disabled={disableButton || inputFieldEmpty}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ecf0f1',
paddingTop: 30,
padding: 8,
},
});
To summarize in brief, the process for implementing WebSockets in a React Native app is as follows:
- The React Native app create a new WebSockets connection and store it in a reference variable,
ws
- The top bar of the app shows the value of the
serverState
variable, the middle part displays a text messages stored in theserverMessages
array, and the bottom bar has an input field that stores typed messages in themessageText
state variable - The submit button only becomes active when the app is connected to WebSockets successfully and there is some text in the input box. We control it using the
disableButton
andinputFieldEmpty
variables. The submit button will be active when bothdisableButton
andinputFieldEmpty
arefalse
- In the
useEffect()
hook, we define all the WebSocket functions - When the WebSockets connection is opened by the server, the
onopen
function is called. This changes the value ofserverState
variable toconnected to the server
anddisableButton
tofalse
, so the top bar of the app shows the connected message - When the connection closes, the
onclose
function is called. This changes theserverState
todisconnected message
anddisableButton
totrue
. At this point, the submit button is no longer active, even if you type a message in the input box, because we can’t send messages to the server - If there is an error, the
onerror
function is called and changes theserverState
to that particular error message - When the server broadcasts messages, the
onmessage
function is called. This will append the received message into theserverMessages
array - We created a
submitMessage
message to send the message to the server, which the server broadcasts to all devices
Here, I embedded two instances of this app: one for Android and another for iOS. You can test that the message sent from one device will be shown on both. Verify that the Node.js server we embedded above is running fine and is not in hibernation mode.
If you encounter problems with the above devices, you can run the code here.
Conclusion
In this tutorial, we showed you how easy it is to create WebSockets in a React Native apps. The code will run on both Android and iOS platforms.
We created a simple broadcast app for this particular demonstration, but there is a lot of scope to expand it. For example, you could set IDs to distinguish between clients and align messages sent by the client to the right side and all others to the left side. This will produce a perfect chat app-like look and feel.
You could also create a form to capture details (such as the user’s name) before starting a chat and show the information beside received messages.
Let us know in comments what kinds of things you’ve built in React Native using WebSockets.
LogRocket: Full visibility into your web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Top comments (0)