skip to Main Content

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


  1. Because you use menuItems as a dependence in the useEffect which inside you call the fetchFriends function which updates the menuItems with setMenuItems which creates an infinity loop, the same for fetchChats

    You need to have an empty dependences array

        useEffect(() => {
            fetchFriends()
        }, [])
    
        useEffect(() => {
            fetchChats()
        }, [])
    
    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
  3. 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

        const fetchFriends = (async () => {
            if (!window.localStorage.getItem('e')) {
                ...
            } else {
                ...
            }
            setMenuItems(...); //this code triggers your useEffect to run
        }
    
        useEffect(() => {
            fetchFriends()
        }, [menuItems])//any change in this variable will cause the use effect to run again
    

    You also made the exact same mistake in your ChatWindow.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search