Search code examples
reactjsflaskreal-time

React for displaying the log file in real-time (Flask backend)


I have a working code (Flask + plain JS) which shows the growing log file in real time. I have a problem converting it to ReactJS.

app.py (Flask, working code, no problems)

@app.route('/stream')
def stream():
    def generate():
        fname="/Users/xxx/tmp/log.txt"
        with open(fname) as f:
            while True:
                yield f.read()
                sleep(1)
    return 
    app.response_class(generate(), mimetype='text/plain')

app.run()

index.html (plain JS, no problems)

<pre id="output"></pre>
<script>
    var output = document.getElementById('output');
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '');
    xhr.send();

    setInterval(function() {
        output.textContent = xhr.responseText;
    }, 1000);
</script>

Below is my attempt to convert it to React.

On MyPage.jsx I put the button "Show Real Time Log". When user clicks this button the Modal Dialog should pop-up and display the log.

File: MyPage.jsx

import Modal from 'react-bootstrap/Modal'
import { Button } from 'react-bootstrap'

const showLog = (e) => {
    render(<ModalLog />,  document.getElementById('output'));
}

const MyPage = () => {
  return (
    <div>
          <div id="output"></div>
          <button type="button" onClick={showLog}>Show Real Time Log</button>
</div
}

function ModalLog() {
  const [show, setShow] = useState(true);

  const handleClose = () => setShow(false);
  const handleShow = () => setShow(true);

  return (
    <>
      <Modal show={show} onHide={handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>Real Time Log</Modal.Title>
        </Modal.Header>
        <Modal.Body> Log file live update goes here </Modal.Body>
        <Modal.Footer>
          <Button variant="primary" onClick={handleClose}>
            Close
          </Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

Questions:

  1. Where to put into MyPage.jsx the JS code currently sitting in my index.html?

  2. When I press the button "Show Real Time Log" the Modal is displayed only 1st time, after I close it never displayed again. I understand what it is because handleClose() was called, but how to fix it?


Solution

  • (1) Where to put into MyPage.jsx the JS code currently sitting in my index.html?

    Simple answer is you don't. It's directly manipulating the DOM and this is a severe react anti-pattern. You would convert it to an useEffect hook to fetch the latest log data and save it to component state, to be rendered.

    const [logs, setLogs] = useState('');
    useEffect(() => {
      // asynchronous function to fetch logs
      const fetchLogs = async () => {
        try {
          const logResponse = await /* logic to fetch log */
          setLogs(logResponse);
        } catch {
          // just don't update state, or set an error message in state,
          // basically anything you want to do to handle errors
        }
      }
    
      // setup interval to fetch logs
      const intervalTimer = setInterval(fetchLogs, 1000);
    
      // function to clean up effect when component unmounts
      return () => clearInterval(intervalTimer);
    }, []); // empty dependency array to run when mounted
    

    (2) When I press the button "Show Real Time Log" the Modal is displayed only 1st time, after I close it never displayed again. I understand what it is because handleClose() was called, but how to fix it?

    You initial state to display the modal is true. I would refactor the logic a bit to have MyPage hold the state if the modal is open or not (instead of the modal). Pass show and handleClose as props to ModalLog. No need to render the modal into another DOM element, I believe the react-bootstrap will handle creating a portal for you. You can simply render it into your component.

    const MyPage = () => {
      const [show, setShow] = useState(false);
    
      const handleClose = () => setShow(false);
      const handleShow = () => setShow(true);
    
      return (
        <div>
          <div id="output"></div>
          <button type="button" onClick={handleShow}>Show Real Time Log</button>
          <ModalLog show={show} onHide={handleClose} />
        </div>
      );
    }
    
    function ModalLog({ show, onHide }) {
      return (
        <>
          <Modal show={show} onHide={onHide}>
            <Modal.Header closeButton>
              <Modal.Title>Real Time Log</Modal.Title>
            </Modal.Header>
            <Modal.Body> Log file live update goes here </Modal.Body>
            <Modal.Footer>
              <Button variant="primary" onClick={handleClose}>
                Close
              </Button>
            </Modal.Footer>
          </Modal>
        </>
      );
    }
    

    Suggestion: Place the log state and logic to fetch and display the logs in the modal as it appears you only care to display them when the modal is open and there's no point in fetching them in the background while the modal is closed.