gradio
gradio copied to clipboard
`gr.State` cannot be passed to JavaScript function
Describe the bug
I want to run an event handler that does some processing and returns a string, then passes that output string to a JavaScript function
So I figured I'd store the string in a gr.State and put it as the input to the JS function, however, the JS function only gets null instead. If I pass a Python callback that does see the resulting state though
Is there an existing issue for this?
- [X] I have searched the existing issues
Reproduction
Place some text in the textbox and click each button in sequence
#!/usr/bin/env python
import gradio as gr
with gr.Blocks() as demo:
textbox1 = gr.Textbox()
btn = gr.Button("Go", variant="stop")
btn2 = gr.Button("Go2", variant="stop")
btn3 = gr.Button("Go3", variant="stop")
def ppr(st):
print("GET: " + str(st))
js = "(x) => { console.log(x); return x; }"
css_state = gr.State("")
btn.click(fn=lambda x: x, inputs=[textbox1], outputs=[css_state]).then(fn=ppr, _js=js, inputs=[css_state], outputs=None)
btn2.click(fn=ppr, _js=js, inputs=[css_state], outputs=None)
btn3.click(fn=ppr, _js=js, inputs=[textbox1], outputs=None)
demo.queue().launch(server_port=9876)
Screenshot
No response
Logs
Python stdout:
GET: asdf!
GET: asdf!
GET: asdf!
JS console:
null
null
asdf!
System Info
3.22.1, Windows, Chromium
Severity
annoying
So state is only stored in the backend only, this is so that state can support binary, non-serializable data. You can maintain frontend state by storing a global variable in window, e.g. window.state_data = {}. Will think about if we should send non-binary state data to the frontend, and how to better document this all
I waste 1 hour to find out a problem is caused by this issue.
If _js can not work with gr.State, and you don't treat it as a bug, don't have plan to fix it, then write it in document.
So state is only stored in the backend only, this is so that state can support binary, non-serializable data. You can maintain frontend state by storing a global variable in window, e.g.
window.state_data = {}. Will think about if we should send non-binary state data to the frontend, and how to better document this all
if state is stored in the backend, when will they be deleted from memory ? When the web page is closed ? Are there any risk of memory leak when there are many users?
@aliabid94 I tried the state solution for gradio_folium and it still doesn't work:
import gradio as gr
from gradio_folium import Folium
from folium import Map, Element, LatLngPopup
import pandas as pd
import pathlib
def click(a):
print('hellp')
print(a)
def inject_javascript(map):
js = """
document.addEventListener('DOMContentLoaded', function() {
map_name_1.on('click', function(e) {
window.state_data = e.latlng
});
});
"""
map.get_root().html.add_child(Element(f'<script>{js}</script>'))
with gr.Blocks() as demo:
map = Map(location=[25.7617, 80.1918])
map._name = "map_name"
map._id = "1"
LatLngPopup().add_to(map)
inject_javascript(map)
fol = Folium(value=map, height=400)
js = "(a) => {console.log(window.state_data); return window.state_data;}" # -> window is null
button = gr.Button("Get results")
button.click(click, inputs=[fol], js=js)
demo.launch()
it's probably a scope problem but still there should be a way to bypass it.
if state is stored in the backend, when will they be deleted from memory ? When the web page is closed ? Are there any risk of memory leak when there are many users?
State is deleted 60 minutes after a user closes the page (to allow for reconnections in case a connection is dropped momentarily)
There is a solution, listen to the change event of gr.State, output to a gr.JSON, then listen to the change event of gr.JSON, js can get the value
Thank you for the workaround @xinyii !
Will close as we designed gr.State to be stored only in the server.