I making a chat app in react. I’m finding trouble in the ChatWindow component that displays all the chats. These chats are called from a backend server. This component is somehow making infinite amount of requests to the backend server which is crashing it. It also buffers the chats multiple times simultaneuosly. I used the network tab in dev tools and it showed 117 reqs in less than 10 secs. I faced the same problem in Sidebar.jsx. See the image: Output of network tab in devtools
Here is Sidebar.jsx:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Menu, Drawer, Button } from 'react-daisyui';
const api = `http://localhost:8000`;
function Sidebar({ onRecipientChange }) {
const [menuItems, setMenuItems] = useState([])
const [nick, setNick] = useState('')
const handleFriendSelect = (nickname, email) => {
onRecipientChange(nickname, email)
}
const fetchFriends = (async () => {
if (!window.localStorage.getItem('e')) {
const response = await axios.get(`${api}/users/profile/friends?email=${window.localStorage.getItem('mail')}`);
const res = response.data.values;
const frnds = JSON.parse(res);
let items = [];
if (Array.isArray(frnds) && frnds.length > 0 && res !== '["Start"]') {
const requests = frnds.map(async (friend) => {
const r = await axios.get(`${api}/users/profile?email=${friend}`);
return r.data.values[0].nickname;
});
const nicknames = await Promise.all(requests);
items = nicknames.map((nickname, index) => (
<Menu.Item onClick={() => handleFriendSelect(nickname, frnds[index])} style={{ borderRadius: 3 }}>
{(nickname) ? nickname : "None"}
</Menu.Item>
));
} else {
items.push(
<Menu.Item key="None" style={{ borderRadius: 3 }}>
None
</Menu.Item>
);
}
setMenuItems(items);
}
})
useEffect(() => {
fetchFriends()
}, [menuItems])
useEffect(() => {
async function fetchNick() {
if (!window.localStorage.getItem('e')) {
const res = await axios.get(`${api}/users/profile?email=${window.localStorage.getItem('mail')}`)
setNick(res.data.values[0].nickname)
}
}
fetchNick()
}, []);
return (
<>
<div>
<Drawer open={true} side={
<>
<Menu className="bg-base-200 rounded-sm text-lg h-full" style={{ width: 250 }}>
<Menu.Title className='text-xl' style={{ color: 'gray' }}>
<span className='flex justify-between'>
{nick}
<Button
color='ghost' size='sm' className='btn-circle'
onClick={
async function logout() {
window.localStorage.removeItem('mail')
window.location = '/login'
}
}
>
</Button>
</span>
</Menu.Title>
<Menu.Item>
<Menu.Title className='text-lg'>Me</Menu.Title>
<Menu.Item style={{ borderRadius: 3 }}>
Profile
</Menu.Item>
<Menu.Item style={{ borderRadius: 3 }}>
Inbox
</Menu.Item>
<Menu.Item style={{ borderRadius: 3 }}>
Friends
</Menu.Item>
</Menu.Item>
<Menu.Item>
<Menu.Title className='text-lg'>Chats</Menu.Title>
{menuItems}
</Menu.Item>
</Menu>
</>
} />
</div>
</>
);
}
export default Sidebar
Here is ChatWindow.jsx:
import { useEffect, useState } from 'react';
import { Button, Navbar, Input, Join, ChatBubble } from 'react-daisyui';
import axios from 'axios'
const api = `http://localhost:8000`;
function ChatWindow({ recipientNickname, recipientEmail }) {
const client = window.localStorage.getItem('mail')
const [chats, setChats] = useState([])
async function fetchChats() {
try {
const response = await axios.get(`${api}/users/messages?client=${client}&recipient=${recipientEmail}`);
if (response.data) {
setChats(response.data);
} else {
setChats([]);
}
} catch (e) {
console.log(e);
}
}
useEffect(() => {
fetchChats()
}, [chats])
const chatBubbles = chats.map((message, index) => (
<ChatBubble key={index} end={message.client === client}>
<ChatBubble.Message>{message.message}</ChatBubble.Message>
</ChatBubble>
))
return (
<div style={{ margin: 250, marginTop: 0, zIndex: 1 }}>
<div>
<Navbar>
<Navbar.Center style={{ alignItems: 'center' }}>
<Button tag="a" color="ghost" className="normal-case text-xl">
{recipientNickname || "Chats"}
</Button>
</Navbar.Center>
</Navbar>
</div>
<div className='chats'>
{chatBubbles}
</div>
<div>
<div style={{ position: 'fixed', bottom: 5, left: 0, right: 0 }}>
<Join>
<Input style={{ width: 1000, marginLeft: 260 }} placeholder='Send a message' className='join-item' />
<Button className='join-item rounded-s' color='success'>
Send
</Button>
</Join>
</div>
</div>
</div>
)
}
export default ChatWindow
Here is the backend route code:
const axios = require(‘axios’)
module.exports = {
route: "users/messages",
method: ‘GET’,
run: async(req, res) => {
const { client, recipient } = req.query
axios.get(require(‘../api’)(‘Messages’))
.then((response) => {
const [headerRow, …rows] = response.data.values;
const Data = rows.map(row => {
const obj = {};
headerRow.forEach((key, index) => {
obj[key] = row[index]
})
return obj
})
const result = Data.filter(entry =>
(entry.client == client && entry.recipient == recipient)
||
(entry.client == recipient && entry.recipient == client)
)
// console.log(result)
res.send(result)
})
}
}
UPDATE: I removed the dependency variables from useEffect, but now the chats don’t load at all in ChatWindow…
3
Answers
Because you use
menuItems
as a dependence in theuseEffect
which inside you call thefetchFriends
function which updates the menuItems withsetMenuItems
which creates an infinity loop, the same forfetchChats
You need to have an empty dependences array
There’s an old adage that computers only do what you tell them to do – you’ve programmed an infinite loop. Setting state inside useEffect will cause it to update and then call useEffect again:
"Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update." – https://legacy.reactjs.org/docs/hooks-effect.html
One of the common mistakes to avoid in a useEffect is updating in the dependancy array within a it. It’s important to understand how the useEffect hook works. By setting setMenuItems(…) within the use effect which runs every time you make a change to menuItems, you are effectively creating an infinite loop.
On your Sidebar you have
You also made the exact same mistake in your ChatWindow.