Send Data Back Into Streamlit

At this stage, we have built an unidirectional data flow where Python arguments are being sent to the React component through JavaScript events and collected into the component’s props. We would now like to retrieve the internal state of the widget back into our Streamlit script using Streamlit.setComponentValue.

The Streamlit.setComponentValue method stores the component’s value internally, then triggers a Streamlit rerun. The Streamlit rerun will retrieve the new component’s value and store it back into the Python variable assigned to your st_custom_slider call.

Head back to CustomSlider.tsx. We want to rerun the Streamlit script on mouse release — we can do this by integrating the Streamlit component value setting as a callback of the onFinalChange event in CustomSlider.tsx:

import React, { useEffect, useState } from "react";
import {
  ComponentProps,
  Streamlit,
  withStreamlitConnection,
} from "./streamlit";
import { Slider } from "baseui/slider";

const CustomSlider = (props: ComponentProps) => {
  const { label, minValue, maxValue } = props.args;
  const [value, setValue] = useState([10]);

  useEffect(() => Streamlit.setFrameHeight());

  // Send data back to Streamlit in onFinalChange event
  return (
    <>
      <h3>{label}</h3>
      <Slider
        value={value}
        onChange={({ value }) => value && setValue(value)}
        onFinalChange={({ value }) => Streamlit.setComponentValue(value)}
        min={minValue}
        max={maxValue}
      />
    </>
  );
};

export default withStreamlitConnection(CustomSlider);

Just to make sure we’re all on the same page, your __init__.py should look like the following:

import os
import streamlit.components.v1 as components

_component_func = components.declare_component(
    "custom_slider",
    url="http://localhost:3001",
)

def st_custom_slider(label: str, min_value: int, max_value: int, key=None):
    component_value = _component_func(label=label, minValue=min_value, maxValue=max_value, key=key)
    return component_value

Let’s put a Streamlit native slider and our custom slider side-by-side in our app.

app.py

import streamlit as st
from streamlit_custom_slider import st_custom_slider

# Streamlit native slider
v = st.slider("Hello world", 0, 100)
st.write(v)

# Streamlit custom slider
v_custom = st_custom_slider('Hello world', 0, 100, key="slider1")
st.write(v_custom)

Compare native with custom slider

What can we see from this gif? The native slider makes the script rerun as long as the slider moves. The custom slider, with Streamlit, reruns only on mouse release and we retrieve the value of the slider on the Python side. So far, so good!

However, there are some issues with our implementation:

  • No default value : While the Streamlit native slider has a way to define a default value, we don’t have this on our CustomSlider, which returns None before the first render of the component.
  • The return value is an array instead of a number : The variable v_custom we get back from st_custom_slider is an array, as our CustomSlider stores its state as an array of number for the baseui Slider (the reason being so that it can also take an array of two numbers to render a RangeSlider). We would prefer st_custom_slider to return an int instead of an array, though we don’t want to do too much type manipulation on the React side.

Fortunately, these two issues can be addressed at the Python wrapper level in __init__.py.