WebSocket connection slows down webpage - javascript

I have an app that uses axios, socket and chagstreams that keeps track of data in mongodb.
When the user clicks the like or dislike button it send a axios.post request and updates the database. Then changestream then picks up on the change and fires a socket.emit() to send the new data to the front. In the front the updated data is stored in a state hook.
Clientside.js
function Home() {
var { username } = useParams();
const { state } = useLocation();
if (state !== null) {
var { validation } = state;
}
const [SearchedData, setSearchedData] = useState();
const [MainData, setMainData] = useState();
const socket = io("ws://localhost:5000");
React.useEffect(() => {
socket.emit("content");
socket.on("get-data", (data) => {
setMainData(data);
});
}, []);
socket.on("Updated", (data) => {
if (JSON.stringify(MainData) !== JSON.stringify(data))
setMainData(data);
});
return (
<div className="App">
<NavBar
CurrentUser={username}
SearchedData={setSearchedData}
MainData = {MainData}
socket={socket}
validation={validation}
/>
{MainData !== undefined ? (
<Content
CurrentUser={username}
SearchedData={SearchedData}
MainData={MainData}
validation={validation}
></Content>
) : (
<Loading></Loading>
)}
</div>
);
}
route.js
router.post("/:username/like", (req,res) => {
const {username, id} = req.body
Comments.findByIdAndUpdate(id, {$set: {score: username}}, {upsert: true}, (err, d) => {
if(err){
res.sendStatus(400)
}
else{
res.sendStatus(200)
console.log(d['_id'] + " Liked")
}
})
})
router.post("/:username/dislike", (req,res) => {
const {username, id} = req.body
Comments.findByIdAndUpdate(id, {$pull: {score: username}}, (err, d) => {
if(err){
res.sendStatus(400)
}
else{
res.sendStatus(200)
console.log(d['_id'] + " DisLiked")
}
})
})
Server.js
const express = require("express");
const app = express();
const socket = require("socket.io");
const server = app.listen(process.env.Port, () => {
console.log("Socket listening " + process.env.Port);
});
const io = socket(server, {
cors: {
origin: "*",
credential: true,
},
});
mongoose
.connect(process.env.MongoDB, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("Database Connected");
})
.catch((err) => {
console.log(err);
});
io.on("connection", (socket) => {
const changeStream = Comments.watch();
changeStream
.on("change", (next) => {
Comments.find({}, (err, save) => {
socket.emit("Updated", save.reverse());
});
})
.on("error", err => {
console.log(err)
})
socket.on("content", (username) => {
var recievedData;
Comments.find({}, function (err, data) {
if (!err) {
recievedData = data;
socket.emit("get-data", recievedData.reverse());
}
});
});
socket.on("user-data", (username) => {
Users.find({ username: username }, (err, save) => {
if (!err) socket.emit("recieve-user-data", save);
});
});
})
Content.js
function Content(props) {
const [MainDisplay, setMainDisplay] = useState();
const [Recusers, setRecusers] = useState()
const [DeleteModal, setDeleteModal] = useState({delete: false, id: ""})
const [DeetsIndex, setDeetsIndex] = useState(-1)
const [EditModal, setEditModal] = useState({edit: false, id: ""})
const onLike = (e) => {
e.preventDefault();
const { id } = e.target.dataset;
const data = {
username: props.CurrentUser,
id: id,
};
axios
.post(process.env.REACT_APP_baseServerurl + "/user/:username/like", data)
};
const onDislike = (e) => {
e.preventDefault();
const { id } = e.target.dataset;
const data = {
username: props.CurrentUser,
id: id,
};
axios
.post(process.env.REACT_APP_baseServerurl + "/user/:username/dislike", data)
};
const DeetsList = (e) => {
const { id } = e.target.dataset
if(DeetsIndex == id)
setDeetsIndex(-1)
else{
setDeetsIndex(id)
}
}
const DeleteConfirmation = (id) => {
setDeleteModal(DeleteModal => ({
delete: true, id: id
}))
setDeetsIndex(-1)
}
const Edit = (id) => {
setEditModal(EditModal => ({
edit: true, id: id
}))
setDeetsIndex(-1)
}
React.useEffect(() => {
if (props.SearchedData !== undefined && props.setSearchedData !== "") {
if(props.SearchedData["users"].length > 0){
setRecusers(props.SearchedData["users"])
}
setMainDisplay(props.SearchedData['comments']);
}
else if(JSON.stringify(MainDisplay) !== JSON.stringify(props.MainData)) {
setMainDisplay(props.MainData);
}
});
return (
<div className="Comment-container">
{(Recusers === undefined || props.SearchedData === undefined || Recusers.length === 0 )? <></> : <DisplayUsers users={Recusers}></DisplayUsers>}
{(DeleteModal.delete)? <Delete DeleteModal={[DeleteModal,setDeleteModal]} ></Delete> : <></>}
{(EditModal.edit)? <EditContent EditModal={[EditModal, setEditModal]} ></EditContent> : <></>}
{MainDisplay !== undefined ? (
MainDisplay.map((data, index) => {
return (
<div className="Comments" id={data["_id"]}>
<div className="Likes">
<div className="like">
<svg
viewBox="0 0 32 32"
data-id={data["_id"]}
xmlns="http://www.w3.org/2000/svg"
onClick={onLike}
>
<path
data-id={data["_id"]}
d="M28,14H18V4c0-1.104-0.896-2-2-2s-2,0.896-2,2v10H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h10v10c0,1.104,0.896,2,2,2 s2-0.896,2-2V18h10c1.104,0,2-0.896,2-2S29.104,14,28,14z"
/>
</svg>
</div>
<p id="score">{data["score"].length}</p>
<div className="dislike">
<svg
viewBox="0 0 24 24"
data-id={data["_id"]}
className="dislike"
xmlns="http://www.w3.org/2000/svg"
onClick={onDislike}
>
<path
data-id={data["_id"]}
d="M18,11H6c-1.104,0-2,0.896-2,2s0.896,2,2,2h12c1.104,0,2-0.896,2-2S19.104,11,18,11z"
/>
</svg>
</div>
</div>
<div className="Comment-header">
<div className="Comment-info">
<div className="inner-header">
<div>
<img alt="profile-pic" src={data["avatar"]}></img>
</div>
<p className="Username">{data["username"]}</p>
</div>
<p>{data["createdAt"]}</p>
</div>
<div className="content">
<p>{data["content"]}</p>
</div>
</div>
<div className="deets">
<img
className="deet-img"
data-id={index}
src={require("./images/deets.png")}
width="40px"
onClick={DeetsList}
></img>
{
(DeetsIndex == index)?
<ul className="deet-list" data-index={index}>
{(props.validation && props.CurrentUser === data["username"])?
<>
<li onClick={() => {DeleteConfirmation(data["_id"])}}>Delete</li>
<li onClick={() => {Edit(data["_id"])}}>Edit</li>
</>
:
<></>
}
<li onClick={() => {DeleteConfirmation(data["_id"])}}>Share</li>
</ul>
:
<></>
}
</div>
</div>
);
})
) : (
<Loading></Loading>
)}
</div>
);
}
Problem:
After a few requests or minutes (idle time) the console displays connection errors in loop and the web browser slows down.
Firefox can’t establish a connection to the server at ws://localhost:5000/socket.io/?EIO=4&transport=websocket&sid=-Y4oqgeERHo7ernXABEn.\
Tried to run the app in Chrome but it has the same slowing down problem without any errors.
In both the browsers if i check the network tab it shows pending requests.
Tried to insert some string from the front end. It works fine the first few times then it begins to slow down. So i guess the problem is some where in the routes part where it sends the request to store the data in the DB cause when i enter the data and check my collection on the mongodb website/ Compass there is no data of such it appears only when the request has been successfully completed. Switching it entirely to sockets also made no difference
I don't think the error is the main problem. It just that the browser slows down..
Update:
I think i solved the problem. It was initially triggering socket.on("Updated") multiple times and with each request it kept on increasing. So right now the performance seems normal. I just replaced socket.on("Updated") with socket.once("Updated")..

Related

Message Unable to show automatically in reciever's side with Socket.io

I'm trying to send real time message with socket.io.
But the problem is that The reciever won't receive the message until i refresh the browser.
i'm getting all the data on the console but not in recievers end
I want to make it a real time message
Below are my codes
Messenger FrontEnd
// Context State
const { friends, setFriends, message, setMessage, authInfo } = useAuth();
const [currentFriend, setCurrentFriend] = useState("");
const [activeUser, setActiveUser] = useState([]);
const [newMessage, setNewMessage] = useState("");
const [socketMessage, setSocketMessage] = useState("");
const { updateNotification } = useNotification();
useEffect(() => {
socket.current = io("ws://localhost:9000");
socket.current.on("getMessage", (data) => {
setSocketMessage(data);
});
}, []);
useEffect(() => {
if (socketMessage && currentFriend) {
if (
socketMessage.senderId === currentFriend._id &&
socketMessage.receiverId === authInfo.profile.id
) {
console.log([...message, socketMessage]); // I'm confused on what to do here
}
}
setSocketMessage("");
}, [socketMessage]);
Socket.io Backend
let users = [];
const addUser = (userId, socketId, userInfo) => {
const checkUser = users.some((u) => u.userId === userId);
if (!checkUser) {
users.push({ userId, socketId, userInfo });
}
};
const userRemove = (socketId) => {
users = users.filter((u) => u.socketId !== socketId);
};
const findFriend = (id) => {
return users.find((u) => u.userId === id);
};
io.on("connection", (socket) => {
console.log("Socket Is Connecting...");
socket.on("addUser", (userId, userInfo) => {
addUser(userId, socket.id, userInfo);
io.emit("getUser", users);
});
socket.on("sendMessage", (data) => {
const user = findFriend(data.receiverId);
if (user !== undefined) {
socket.to(user.socketId).emit("getMessage", {
senderId: data.senderId,
senderName: data.senderName,
receiverId: data.receiverId,
createAt: data.time,
message: {
text: data.message.text,
image: data.message.image,
},
});
}
});

how to get socket.on("test", { count)) current value when component mounted?

import ...
let socket;
const Game = () => {
const ...
const [userCount, setUserCount] = useState(0);
const scrollToBottom = () => {
endToMessages.current?.scrollIntoView({ behavior: "smooth" });
};
const { _id, username } = JSON.parse(localStorage.getItem("user")).user;
useEffect(() => {
socket = io(process.env.REACT_APP_BASE_URL);
socket.emit("game_lobby", { id: _id, username, room: gameInfo.name });
return () => socket.disconnect();
}, [_id, username, gameInfo.name]);
useEffect(() => {
socket.on("message", ({ user, text }) => {
setMessages((prev) => [...prev, { user, text }]);
scrollToBottom();
});
socket.on("user_count",({count}) => {
setUserCount(count);
})
}, []);
console.log("Current user count",userCount);
const sendMessage = () => {
...
};
return (
<div className="game section">
...
</div>
);
};
export default Game;
Server side:
import { Server } from "socket.io";
const socketApi = (server) => {
const io = new Server(server, {
cors: {
origin: ["https://mook-f2b4e.web.app", "http://localhost:3000"],
methods: ["GET", "POST"],
},
});
io.on("connection", (socket) => {
socket.on("disconnect", () => {
console.log(`user ${socket.id} had left`);
});
socket.on("game_lobby", async ({ id, username, room }) => {
console.log("We have a new connetion.");
socket.join(room);
const roomClients = await (await io.in(room).fetchSockets()).length;
console.log(roomClients);
io.to(room).emit("user_count", { count: roomClients });
socket.on("disconnect", () => {
io.to(room).emit("user_count", { count: roomClients });
});
socket.emit("message", {
user: "Admin",
text: `${username} welcome to ${room} room`,
});
});
socket.on("send_message", ({ name, message, room }) => {
io.to(room).emit("message", { user: name, text: message });
});
});
return io;
};
export default socketApi;
Hi all.When I try to get user count when component mount but I can't.First time when component did mount I get 0 value. If someone else joins the room then I can get its current value. If I could explain properly I mean let's say there are 3 people in the room and I joined that room later. Now there are 4 people in the room but I got 0 value from console.log("Current user count", userCount) if the 5th person joins the room then I can get the current value from the server.
I believe you would need the server to have a variable to keep track of active users. So connection increment a variable for onlineUsers, then on disconnect subtract one from the onlineUsers
See reference here => https://stackoverflow.com/a/10276446/3124019

Fetch request stuck on pending and then doesn't go through

I'm making a full stack Rick and Morty application. Characters on the screen and the user can login and click on them to add them to favorites and then click on them on the favorites page to delete them from the favorites page.
The application works but crashes after a few minutes saying that a fetch request didn't work. In network section of the developer tools, these requests to add or delete characters are coming up as (pending) and then coming up as failures like two minutes later. At the same time, the requests are working from the perspective of the application, meaning that if I add or delete characters as a user and then logout and log back in, the changes are still there. The register and login requests to the backend are working normally with statuses of 200 as well. What's happening here?
The backend:
const express = require('express');
const application = express();
const mongoose = require('mongoose');
application.use(express.json());
mongoose.connect('process.env.DATABASE_PASSWORD')
.then(console.log('Connected to database'));
const db = mongoose.connection;
const port = process.env.PORT || 8080;
application.post('/register', (request, response) => {
const username = request.body.username;
const password = request.body.password;
const favorites = [];
db.collection('data').insertOne({
username,
password,
favorites,
});
});
application.post('/login', async (request, response) => {
const username = request.body.username;
const password = request.body.password;
const findUser = await db.collection('data').findOne({
username,
password,
});
if (findUser) {
response.send({ message: 'Welcome, ' + username + "!", user: username, favorites: findUser.favorites });
} else {
response.send({ message: 'Login unsuccessful'});
}
});
application.post('/addFavorite', (request, response) => {
const userNow = request.body.username;
const favoritesHere = request.body.favoritesCopy;
console.log({userNow, favoritesHere});
db.collection('data').updateOne(
{ username: userNow },
{ $set: { favorites: favoritesHere }},
)
});
application.post('/deleteFavorite', (request, response) => {
const userNow = request.body.username;
const favoritesHere = request.body.theData;
db.collection('data').updateOne(
{ username: userNow },
{ $set: { favorites: favoritesHere }},
);
});
application.listen(port, () => {
console.log('Application listening');
});
The frontend fetch add request (the delete request is similar):
import React, { useState, useEffect } from 'react';
import logo from '../rickandmortylogo.png';
import { useSelector, useDispatch } from 'react-redux';
import { addFavorite } from '../index.js';
const Body = () => {
const [characters, setCharacters] = useState([]);
const [currentName, setCurrentName] = useState('Placeholder');
const [nameInput, setNameInput] = useState('');
const [locationInput, setLocationInput] = useState('');
const [loading, setLoading] = useState(true);
const favorites = useSelector(state => state.favoritesList);
const userNow = useSelector(state => state.currentUser);
const loggedIn = useSelector(state => state.loggedIn);
const dispatch = useDispatch();
useEffect(() => {
let isMounted = true;
let url = 'https://rickandmortyapi.com/api/character/';
let array = [];
const getData = async () => {
for (let i = 1; i < 4; i++) {
let response = await fetch(url);
let data = await response.json();
for (let j = 0; j < 20; j++) {
array.push(data.results[j]);
}
url = data.info.next;
}
if (isMounted) {
setCharacters(array);
setLoading(false);
}}
getData();
return () => {
isMounted = false;
}
}, []);
const readInput = (e) => {
setNameInput(e.target.value);
}
const readLocationInput = (e) => {
setLocationInput(e.target.value);
}
const addData = (a, b, c, d) => {
const array = [a, b, c, d];
const favoritesCopy = [...favorites];
favoritesCopy.push(array);
dispatch(addFavorite(array));
if (loggedIn === true) {
fetch('/addFavorite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
favoritesCopy,
username: userNow,
}),
});
}
};
return (
<div className="pt-5">
<div className="text-center mt-5">
<img src={logo} className="img-fluid" />
</div>
<h2>Click on a character here to add them to your favorites. Choose "Check Favorites" in the menu bar to see your favorites and "Search Characters" to come back.</h2>
<div className="all">
<h4>Search by name:</h4>
<input onChange={readInput} />
<h4>Search by location:</h4>
<input onChange={readLocationInput} />
<br />
<div className="row m-1">
{loading ? 'Loading can take a few seconds. Your Rick and Morty experience will be ready soon!' : characters.filter((item) => {
if (nameInput == "") {
return item;
} else {
if (item.name.toLowerCase().includes(nameInput.toLowerCase())) {
return item;
}
}
}).filter((item) => {
if (locationInput == "") {
return item;
} else {
if (item.location.name.toLowerCase().includes(locationInput.toLowerCase())) {
return item;
}
}
}).map((item, id) => {
return (
<>
<div className="col-md-4 border border-dark rounded" id="square" onClick={() => addData(item.name, item.image, item.location.name, item.status)}>
<h2>{item.name}</h2>
<img src={item.image} className="border rounded" />
<h4>Location: {item.location.name}</h4>
<h4>Status: {item.status}</h4>
</div>
</>
)
})}
</div>
</div>
</div>
);
};
export default Body;
You never end the request. You don't send anything in the response and don't call response.end either, nor next. That's why your request never ends.
Here are some examples:
Success message with content
res.status(200).json({ success: true});
Success message without content
res.sendStatus(204);
Of course requests are pending, you never send anything on related actions:
Use res.send and send something, or at least in case of success, send a success status like:
204 for a no content success operation, like a DELETE for example.
201 for a POST operation creating a new resource.
5xx for errors

How do I use a setTimeout to limit the time in a fetch request? [duplicate]

This question already has answers here:
Fetch API request timeout?
(14 answers)
Closed 4 years ago.
I'm working on a React game search app that pulls API data from a third-party source. When the user searches an input that can't be found, the fetch just hangs there without stopping. Basically what I'm stuck on figuring out how to cancel the fetch request onces a setTimeout if finished after say, 10 seconds. If no response is received by then I want it to be canceled and render an error message. Thanks for your help in advance!
class Search extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
games: [],
error: false,
loading: false
}
}
updateInput = (event) => {
this.setState({
title: event.target.value
});
}
handleGames = (search) => {
const proxyUrl = `https://cors-anywhere.herokuapp.com/`;
const key = `8cd10a7136710c1003c8e216d85941ace5a1f00e`;
const endpoint = `https://www.giantbomb.com/api/search/?api_key=`;
const url = `${proxyUrl}${endpoint}${key}&format=json&resources=game&query=${search}&limit=30`;
this.setState({ loading: true }, () => {
fetch(url)
.then(res => res.json())
.then(data => {
const response = data.results;
response.forEach(game => {
this.setState(prevState => ({
games: prevState.games.concat(game),
loading: false
}))
});
}).catch(error => {
console.log('Request failed', error);
});
this.setState({
games: []
})
})
}
handleSubmit = (e) => {
const { title } = this.state;
e.preventDefault();
if (!title) {
this.setState({ error: true })
} else {
this.setState({ error: false })
this.handleGames(title);
}
}
render() {
const { games, error, loading } = this.state;
return (
<div className="App">
<div className="search-bar">
<form>
<input
className="input-field"
type="text"
placeholder="Search Game"
onChange={this.updateInput}
/>
<button
className="search-button"
onClick={this.handleSubmit}
>Search</button>
</form>
<span className="error">{error ? "You kind of need to type something first, genius." : ""}</span>
</div>
<div className="games-container">
{loading ? (
<div className="loading-div">
<i className="fa fa-3x fa-spinner fa-spin" />
<p className="loading">Loading....</p>
</div>
) : (
games.map(game => {
return <Game
key={game.id}
game={game}
icon={game.image.icon_url}
gameTitle={game.name}
/>
})
)
}
</div>
</div>
);
}
}
Instead of directly using fetch you can nest it inside a Promise. You will find a lot of implementation doing a search. That's one I used.
const advFetch = (url, ...) => {
const TIMEOUT = 10000;
let didTimeOut = false;
return new Promise(function(resolve, reject) {
const timeout = setTimeout(() => {
didTimeOut = true;
reject(new Error('Request timed out'));
}, TIMEOUT);
fetch(url, ...).then(function(response) {
clearTimeout(timeout);
if (!didTimeOut) {
resolve(response);
}
})
.catch(function(err) {
if (didTimeOut) {
return;
}
reject(err);
});
})
}
Note: the fetch isn't really cancelled, this is not possible (not true anymore, see below!), it will continue until network timeout is reached, but it will be ignored by your application.
2022 update
On modern browsers it's finally possible to also cancel a fetch/promise by using an AbortController.

how to search endpoint from the server using Star Wars SWAPI API

I am trying to add a search query to the server-side endpoint, which calls swapi - the Star Wars API https://swapi.co/ and lists people by name.
Here's what the fetch call to the backend in App.js looks like (I am using reactJS framework for that):
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
searchResult: [],
}
}
searchPersonByName = (event) => {
fetch('/people/?search='+ event.target.value)
.then(response => response.json())
.then(response => {
//let searchResult = JSON.parse(responseBody).results;
console.log(response);
this.setState({ searchResult: response.results });
})
}
render() {
return (
<div className="pageStyle">
<div className="searchBar">
<input type="text"
placeholder="search for a person"
onChange={this.searchPersonByName}>
</input>
{Object.keys(this.state.searchResult).map((item, i) => (
<li key={i}>
<span>{this.state.searchResult[item].name}</span>
</li>
))}
</div>
</div>
);
}
}
export default App;
on the backend:
//Dependencies
const swapi = require('swapi-node');
const express = require('express'); //express server
const app = express();
app.use(express.static('public'))
//Search people endpoint
//format of the search string:
// https://swapi.co/api/people/?search=
app.get('/people', (req, res) => {
let query = req.query.search;
console.log(query);
swapi.get('https://swapi.co/api/people/?search=' + query).then((result) => {
console.log(result.results);
let results = result.results;
res.send({ results });
}).catch((err) => {
console.log(err);
});
});
//server listening on specified port
app.listen(4000, () => console.log('Listening on port 4000!'))
Right now the search query return the people from the first page only. What is missing?
You are not passing a search term to the backend with your fetch request.
If you really want to search for every change in the input field, you could use the event.target.value as search term.
searchPersonByName = event => {
fetch(`/people?search=${event.target.value}`)
.then(response => response.json())
.then(response => {
this.setState({ searchResult: response.results });
});
};
You also don't need to specify the query parameters in the backend route.
app.get('/people', (req, res) => { ... })
fetch call in App.js
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
searchResult: [],
}
}
searchPersonByName = (event) => {
fetch('/people/?search='+ event.target.value)
.then(response => response.json())
.then(response => {
//let searchResult = JSON.parse(responseBody).results;
console.log(response);
this.setState({ searchResult: response.results });
})
}
render() {
return (
<div className="pageStyle">
<div className="searchBar">
<input type="text"
placeholder="search for a person"
onChange={this.searchPersonByName}>
</input>
{Object.keys(this.state.searchResult).map((item, i) => (
<li key={i}>
<span>{this.state.searchResult[item].name}</span>
</li>
))}
</div>
</div>
);
}
}
export default App;
and backend:
//Dependencies
const swapi = require('swapi-node');
const express = require('express'); //express server
var bodyParser = require('body-parser');
const app = express();
app.use(express.static('public'));
app.use(bodyParser.json({ type: 'application/json' }));
var API_URL = 'http://swapi.co/api/';
//Search people endpoint
//format of the search string:
// https://swapi.co/api/people/?search=
app.get('/people', (req, res) => {
let query = req.query.search;
console.log(query);
swapi.get('http://swapi.co/api/people/?search=' + query).then((result) => {
console.log(result.results);
let results = result.results;
res.send({ results });
}).catch((err) => {
console.log(err);
});
});
//server listening on specified port
app.listen(4000, () => console.log('Listening on port 4000!'))

Categories