Streamlit Player

@okld @randyzwitch

Hello,

Thanks for sharing this code which I found useful on two counts: using React (my first time) and the Streamlit component wrapper with property passing and eventing (I hadnā€™t understood how that works until I understood Reactā€™s component rendering life cycle).

Iā€™m using the React app to forward an API call to a service (currently a simple Flask app with a /api/ping endpoint). In the Streamlit app browser dev tools I can see the ping return data, but finding it impossible to return the data in the fetch (tried axios too), and so canā€™t send this data back to Streamlit.

Hereā€™s the React code. I think Iā€™ve set all CORS headers correctly in the fetch(). Do you have any ideas? (There is a React proxy forwarding URL in package.json for the /api/ping API call.)

import React, { useState, useEffect } from 'react';
import './App.css';
import {
  withStreamlitConnection,
  ComponentProps,
  Streamlit,
} from "streamlit-component-lib";

let numClicks = 0
let hostname = 'No Name'
let action = 'Go!'
let message = 'No Message'

function PingApp(props: ComponentProps) {

  if ('hostname' in props.args) {
    hostname = props.args.hostname
    delete props.args.hostname
  }
  if ('action' in props.args) {
    action = props.args.action
    delete props.args.action
  }
  if ('initial_state' in props.args) {
    message = props.args['initial_state']['message']
    delete props.args.initial_state
  }

  if ("events" in props.args) {
    props.args.events.forEach((event: string) => {
      props.args[event] = (data?: any) => {
        Streamlit.setComponentValue({
          name: event,
          data: data
        })
      }
    })

    delete props.args.events
  }

  useEffect(() => {
    Streamlit.setFrameHeight()
  })

  const [state, setState] = useState({hostname: hostname, numClicks: numClicks, message: message, isError: false, error: ''})
  const [ping, setPing] = useState('No Ping')
  const [clicks, setClicks] = useState(numClicks)

  useEffect(() => {
    // https://javascript.info/async
    fetch('/api/ping', {
      credentials: 'same-origin',
      mode: 'no-cors',
      headers: {
        'Accept':'text/plain',
        'Content-Type': 'text/plain',
        'Access-Control-Allow-Origin': '*'
      }
    })
    .then(resp => resp.text())
    .then(data => setPing(data))

    message = ping + '(' + numClicks +')'

  }, [clicks, ping]);

  useEffect(() => {
    setState({hostname: hostname, numClicks: numClicks, message: message, isError: false, error: ''})
  }, [clicks, ping])

  useEffect(() => {
    Streamlit.setComponentValue({name: 'onStatusUpdate', data: state})
  }, [state])

  const handleClick = async () => {
    setClicks(numClicks += 1)
  }
  
  return (
    <div className="App">
      <header className="App-header">
          <div>
            <span>App Host: {hostname}</span>
          </div>
            <button onClick={handleClick}>{action}</button>
          <div>
            <span>Ping Message: {state.message}</span>
          </div>
      </header>
    </div>
  );
}

export default withStreamlitConnection(PingApp)
1 Like