Wrap input to and output from React component

For an improved user experience, we would prefer to stick to single numbers for the slider st_custom_slider(value: int) -> int. Let’s also add a range slider which takes tuples of two numbers st_range_slider(value: Tuple[int, int] -> Tuple[int, int].

On the React side, the baseui Slider requires an array of one or two numbers in the value prop to render as a Slider or Range slider depending on the size of the input. This means figuring out if the Python side passed a single number or a tuple of two numbers to build an array of one or two elements in Javascript.

Instead of preprocessing numbers & tuples to an array on the React side, let’s let the Python wrapper methods st_custom_slider and st_range_slider bear the responsibility of conforming input/output arguments to the React entry point. By doing this, the React View layer will be responsible for rendering the interactive component on the browser.

Last but not least, let’s destructure our input with a Typescript “interface” to validate data types sent from Python.

Edit the public functions in __init__.py:

import streamlit.components.v1 as components

from typing import Tuple

# Now the React interface only accepts an array of 1 or 2 elements.
_component_func = components.declare_component(
    "custom_slider",
    url="http://localhost:3001",
)

# Edit arguments sent and result received from React component, so the initial input is converted to an array and returned value extracted from the component
def st_custom_slider(label: str, min_value: int, max_value: int, value: int = 0, key=None) -> int:
    component_value = _component_func(label=label, minValue=min_value, maxValue=max_value, initialValue=[value], key=key, default=[value])
    return component_value[0]

# Define a new public method which takes as input a tuple of numbers to define a range slider, and returns back a tuple.
def st_range_slider(label: str, min_value: int, max_value: int, value: Tuple[int, int], key=None) -> Tuple[int, int]:
    component_value = _component_func(label=label, minValue=min_value, maxValue=max_value, initialValue=value, key=key, default=value)
    return tuple(component_value)

app.py

import streamlit as st
from streamlit_custom_slider import st_custom_slider
from streamlit_custom_slider import st_range_slider

v_custom = st_custom_slider('Hello world', 0, 100, 50, key="slider1")
st.write(v_custom)

# Add a range slider
v_custom_range = st_range_slider('Hello world', 0, 100, (20, 60), key="slider2")
st.write(v_custom_range)

Finally on the CustomSlider.tsx:

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

/**
 * We can use a Typescript interface to destructure the arguments from Python
 * and validate the types of the input
 */
interface PythonArgs {
  label: string
  minValue?: number
  maxValue?: number
  initialValue: number[]
}

/**
 * No more props manipulation in the code.
 * We store props in state and pass value directly to underlying Slider
 * and then back to Streamlit.
 */
const CustomSlider = (props: ComponentProps) => {

  // Destructure using Typescript interface
  // This ensures typing validation for received props from Python
  const {label, minValue, maxValue, initialValue}: PythonArgs = props.args;
  const [value, setValue] = useState(initialValue);

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

  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)

Have a look at the result! A Slider and a Range Slider which trigger a Streamlit rerun on mouse release.

Slider and range slider

Wrapping things like that makes it easy to have one single interface to our React component, and multiple public functions which manipulate input & output for the user to this unique interface.

You’re welcome to grab the code result from the main branch of the project on https://github.com/andfanilo/streamlit-custom-slider.