Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear clickable component value on rerun? #7

Open
lansing opened this issue Feb 22, 2023 · 10 comments
Open

Clear clickable component value on rerun? #7

lansing opened this issue Feb 22, 2023 · 10 comments

Comments

@lansing
Copy link

lansing commented Feb 22, 2023

I have observed that, once a clickable component is clicked, the value of the component (now set to the id of the a that was clicked) is persisted indefinitely for the session. In other words, if I click a link inside clickable with id="event", then the value of clickable in the Python script will be event not just for the rerun following the click, but for every rerun afterwards as I interact with other components.

I assume this is the intended behavior, but it's an issue for my use case. I only want to trigger some behavior one time after the clickable is clicked (essentially I just want the same behavior as st.button). Is that possible with this component? I'm pretty new to Streamlit so maybe I'm missing something obvious.

I created a super simple streamlit app to reproduce the behavior and demonstrate my desired use case:

https://github.com/lansing/st-click-detector-issue

In the above app, I want to click one of the links, updating the session_state that the number box is bound to (changing the number to 0), then continue to increment the number box. But that's not possible because the clickable code block is getting executed on every subsequent rerun, resetting my number to 0.

I am currently using a hacky workaround that stores a last_clicked_event in the session_state and treats the clickable as un-clicked if its value is equal to st.session_state.last_clicked_event, but that obviously means you can't click the same event twice in a row.

Thanks, appreciate the contribution.

@vivien000
Copy link
Owner

Hi @lansing. I'm not a Streamlit expert but I think that giving a specific key to a component leads to having this component's state persist from one session to another.

In your code, if you replace the static keys with different values for each session, you'll be able to increase the counter (I understand that's what you want). Here is one way to do it:

import random # <------
import streamlit as st
from st_click_detector import click_detector


html = """
    <a href="#" id="event">First Click me</a>
    """

with st.sidebar:
    clicked_result = click_detector(html, key=str(random.randint(0, 1e12))) # <------
    if clicked_result != "":
        st.session_state.number = 0

with st.container():
    clicked_result_container = click_detector(html, key=str(random.randint(0, 1e12))) # <------
    if clicked_result_container != "":
        st.session_state.number = 0

    st.number_input("Then Increment me", value=1, key="number")
    st.write(f"st.session_state_number: {st.session_state.number}")

@nikodraca
Copy link

I'm having the same issue as OP, and I don't believe the above fixes the problem: making the key a random ID every time doesn't seem to register the click at all. What I'm trying to achieve (and I think OP) is:

  • link is clicked, variable is set
  • rest of streamlit app runs
  • variable is unset

@nikodraca
Copy link

nikodraca commented Aug 30, 2023

FWIW I got around this by keeping track of what links were clicked (I believe similar to what OP is describing):

import streamlit as st
from st_click_detector import click_detector

if "links_clicked" not in st.session_state:
    st.session_state.links_clicked = {}

content = "<a href='some-url'>My Link</a>"
link_clicked = click_detector(content, key="key")

if link_clicked != "" and link_clicked not in st.session_state.links_clicked:
    # Do something only once here...
    st.session_state.links_clicked[link_clicked] = True

This let the click detector act as more of an event listener 😄

@mylank
Copy link

mylank commented Aug 31, 2023

@nikodraca this is also how I solved it, however there is one slight problem as you cannot check if the link is clicked a second time as its value doesn't change in between. There is a solution to this by setting the value back to null on the frontend side, according to this discussion, however this triggers rerendering of the app twice and caused issues for me in combination with other widgets.

I did solve this in a hacky way yesterday by changing the onclick functionality as follows:

  for (let i = 0; i < links.length; i++) {
    if (links[i].id !== "") {
      links[i].onclick = function (): void {
        if (curr_val == links[i].id) {
          curr_val = links[i].id + '#'
        } else {
          curr_val = links[i].id
        }
        Streamlit.setComponentValue(curr_val)
      }
    }
  }

What this does is it keeps in mind the last value clicked and if the current clicked value is the same as the last value, it adds a "#" to the end. Then on the python side the value for link_clicked will change between got_clicked and got_clicked# (assuming the id of the link is got_clicked), just checking for this change and stripping away the # (i.e. link_clicked.removesuffix("#")) now also allows you to check for consecutive clicks.

Hope this helps! 🤞

@parthplc
Copy link

parthplc commented Oct 25, 2023

setComponentValue

Hey @nikodraca , you solution doesn't seem to register the click at all. Could you help out please?

@nikodraca
Copy link

setComponentValue

Hey @nikodraca , you solution doesn't seem to register the click at all. Could you help out please?
My solution did not change any JS code, just the python snippet above (which has limitations of course)

@PawelFaron
Copy link

PawelFaron commented Feb 1, 2024

@mylank

curr_val 

The code does not seem to work. The curr_val is always empty and the condition if (curr_val == links[i].id) is newer met. Could you create a branch with your code?

@hhxc-0
Copy link

hhxc-0 commented Mar 24, 2024

I believe it would be better if a feature can be added to detect single clicks, provided there are no technical issues. Using a single-click detector as a selector by tracking the last clicked element with session state is easier than using a selector to detect single clicks, as discussed previously.

@hhxc-0
Copy link

hhxc-0 commented Mar 25, 2024

I think we can simply reset the clicked state by rerunning and using a different key. Here is my solution (modified from nikodraca's solution):

import streamlit as st
from st_click_detector import click_detector
import time

if "link_key1" not in st.session_state:
    st.session_state.link_key1 = 0
if "link_key2" not in st.session_state:
    st.session_state.link_key2 = 10000

content1 = "<a href='some-url1' id='id1'>My Link1</a>"
content2 = "<a href='some-url2' id='id2'>My Link2</a>"
link_clicked1 = click_detector(content1, key=st.session_state.link_key1)
link_clicked2 = click_detector(content2, key=st.session_state.link_key2)

if link_clicked1 != "":
    # Do something here...
    st.write('link1 clicked')
    st.session_state.link_key1 += 1
    time.sleep(1)
    st.rerun()
else:
    # Do something here...
    st.write('link1 not clicked')

if link_clicked2 != "":
    # Do something here...
    st.write('link2 clicked')
    st.session_state.link_key2 += 1
    time.sleep(1)
    st.rerun()
else:
    # Do something here...
    st.write('link2 not clicked')

It can support multiple click-detectors and won't listen to every event.

hadariru added a commit to hadariru/st-click-detector that referenced this issue Apr 3, 2024
mylank solution for multiple clicks
vivien000#7
@hadariru
Copy link

hadariru commented Apr 4, 2024

@mylank @PawelFaron
Did you find out how to create curr_val?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants