3

I'm trying to make a simple script to push events from a python app to a client. I made a Console React component, which uses SocketIO to receive the events, and I'm pushing the messages with Flask SocketIO.

This is my app.py:

from flask import Flask, request
from flask_socketio import SocketIO, send

app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins='*')

@app.route('/send/<int:n>')
def send_n(n):
    for i in range(n):
        socketio.emit('console', str(i+1))
        socketio.sleep(0.1)
    return 'OK'


if __name__ == '__main__':
    socketio.run(app)

The function send_n(n) simply pushes n events to the client spaced 0.25s.

This is the simplified Console component (it reproduces the undesired behavior):

import { useState, useEffect } from 'react'
import '../styles/console.css'
import { Socket } from 'socket.io-client'

interface ConsoleProps {
    socket: Socket
}

export default function Console(props: ConsoleProps) {
    const [messages, setMessages] = useState<string[]>([])

    useEffect(() => {
        props.socket.on('console', msg => {
            setMessages([...messages, `${new Date().toLocaleTimeString()} ${msg}`])
        })
    })

    return (
        <div className='console' style={{ height: '200px' }}>
            <ul>
                {messages.map((msg, i) => <li key={i}>{msg}</li>)}
            </ul>
        </div>
    )
}

It receives the already connected socket via props.

The component renders fine until it has ~10 elements, but then it slows down and by the 13th line it freezes, and I can't even close the tab without having to kill the process first.

Flask's debugger registers the message for every line rendered, but the lines not rendered (I tried with 25 as value for n) appear to have never been sent.

So I have 2 questions:

  1. Why is flask not sending every message? Some kind of bad queuing?
  2. Why does react freezes when the backend socket freezes?

Also, if I make msg a timestamp, the first messages render quickly, but the difference between the backend timestamp and the React timestamp grows very quickly with the number of messages.

Adcade
  • 151
  • 7
  • 1
    I'm not sure how much this contributes to the problem, but your useEffect callback is register a listener for an event and is not cleaning it up, so every time the component re-renders you'll be adding duplicate handlers, all for the same event. – Miguel Grinberg Jun 12 '21 at 15:18
  • I'm just learning React, so probably the code has some bugs. Thanks for pointing that out. What should I mdo to fix that? maybe the excess of listeners is what makes the code slow – Adcade Jun 13 '21 at 02:04
  • Can't be explained in a comment, but there are many tutorials about using Socket.IO with React. Example: https://dev.to/bravemaster619/how-to-use-socket-io-client-correctly-in-react-app-o65. – Miguel Grinberg Jun 13 '21 at 13:20

1 Answers1

0

I solved it following @Miguel's comment.

Long story short, the useEffect() function is executing on every render, and every time it executes it adds a new handler without deleting the previous one, so after a few renders things get out of control exponentially. So I just added a line to delete the handler when it finishes.

useEffect(():any => {
    props.socket.on('console', msg => {
        setMessages([...messages, `${new Date().toLocaleTimeString()} ${msg}`])
    })
    return () => props.socket.off('console')
})

And now it is rendering without problems.

Adcade
  • 151
  • 7