pythontypescriptonclicktabsstreamlit

Why is onClick misbehaving and sometimes not showing data behind it?


New to typescript and have been using it for 2 days now mainly to build a custom component for my streamlit app. I have created the below navigation bar and the custom component is a tab that can be clicked on the sidebar though it is behaving strangely. When I click on the component tab, sometimes it loads the corresponding page and at times it does not as demonstrated below:

custom navigation bar gif

I have a hunch it might be how I wrote the typescript code, perhaps the onClick function. Is there a better way to do this so as to:

  1. return the value input labelName via Streamlit.setComponentValue()
  2. make sure that upon clicking on the component tab, it actually loads the page behind it? I have placed the python code for how it should behave below:

Typescript component

import {
  Streamlit,
  StreamlitComponentBase,
  withStreamlitConnection,
} from "streamlit-component-lib"
import React, { ReactNode } from "react"

interface State {
  navLabel: string|number
}

class MyComponent extends StreamlitComponentBase<State> {
  public state = {navLabel: ""}

  public render = (): ReactNode => {

    const labelName = this.props.args["name"]
    const iconName = this.props.args["iconName"]

    const { theme } = this.props
    const styles: React.CSSProperties = {}

    return (
      <div className="navtab" onClick={this.onClicked}> 
        <div className="content">
        <input type="radio" name="indicator" id="input-data"/>
        <div className="list">
          <label htmlFor="input-data" className="input-data">              
            <span><i className="material-icons">{iconName}</i></span>
              <span className="text-display">{labelName}</span>
          </label>
          </div>
          </div>
        </div>
      
      
        )
      }

      private onClicked = (): void => {
        this.setState(
          prevState => ({navLabel: this.props.args["name"]}),
          () => Streamlit.setComponentValue(this.state.navLabel)
        )
      }

Python execution code

import streamlit as st
import streamlit.components.v1 as components

def my_component(name, iconName, tabIndex, key=None):
   
    component_value = _component_func(name=name, iconName=iconName, tabIndex=tabIndex, key=key, default='Option')

    # We could modify the value returned from the component if we wanted.
    # There's no need to do this in our simple example - but it's an option.
    return component_value

with st.sidebar:
    test = my_component(name='Dashboard', iconName='dashboard', tabIndex=1, key="1")
    test_2 = my_component(name='Data Analysis', iconName='insights', tabIndex=2, key="2")
    test_3 = my_component(name='Testing', iconName='business', tabIndex=3, key="3")
    
if test == 'Dashboard':
    st.title("Dashboard")
    st.write('Name of option is {}'.format(test))

elif test_2 == 'Data Analysis':
    st.title("Data Analysis")
    st.write('Name of option is {}'.format(test_2))

elif test_3 == "Testing":
    st.title("Third one")

Solution

  • I was able to sort this out using the li and ul element:

    import {
      Streamlit,
      StreamlitComponentBase,
      withStreamlitConnection,
    } from "streamlit-component-lib"
    import React, { ReactNode } from "react"
    
    interface State {
      label: string,
      icon: string
    }
    
    class MyComponent extends StreamlitComponentBase<State> {
    
      public render = (): ReactNode => {
    
        const labelName:string[] = this.props.args["name"]
        const iconName:string[] = this.props.args["iconName"]
    
        let data:any[] = [];
        iconName.forEach((v,i) => 
          data= [...data, {"id":i+1, "label": labelName[i], "icon":v}]
        )   
    
        this.state = {
                      icon:data[0].icon,
                      label:data[0].label
                    }
        const res = data.map(({id, icon, label}) => (
                                    <span>
                                      <li className="tab"                                  
                                        key={id}
                                        onClick={() => this.setState(
                                          prevState => ({icon:icon, label:label}),
                                                        () => Streamlit.setComponentValue(label)
                                      )}><i className="material-icons">{icon}</i><span className="labelName">{label}</span></li></span>
                                    ))
    
          return (
            
            <div className="navtab">
              <ul className="tab-options">
                {res}
              </ul> 
            </div>
        )
      }
     }