Skip to content

Add watch function to gr.HTML js_on_load#12988

Open
aliabid94 wants to merge 10 commits intomainfrom
add_watch_to_custom_html
Open

Add watch function to gr.HTML js_on_load#12988
aliabid94 wants to merge 10 commits intomainfrom
add_watch_to_custom_html

Conversation

@aliabid94
Copy link
Collaborator

New function in js_on_load that allows for reactive functions like $effect in svelte:

    watch_html = gr.HTML(
        value=0,
        html_template="""
        <div>
            <div>Will 'submit' at 10, currently ${value}</div>
            <button class="inc">+1</button>
            <button class="reset">Reset</button>
        </div>
        """,
        js_on_load="""
        element.querySelector('.inc').addEventListener('click', () => {
            props.value++;
        });
        element.querySelector('.reset').addEventListener('click', () => {
            props.value = 0;
        });

        watch('value', () => {
            if (props.value === 10) {
                trigger('submit');
            }
        });
        """,
        elem_id="watch_demo",
    )

@aliabid94 aliabid94 requested a review from freddyaboulton March 9, 2026 19:51
@gradio-pr-bot
Copy link
Collaborator

gradio-pr-bot commented Mar 9, 2026

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview
Website ready! Website preview
Storybook ready! Storybook preview
🦄 Changes detected! Details

Install Gradio from this PR

pip install https://gradio-pypi-previews.s3.amazonaws.com/ade077eed2857e48507ca3867735d9a5ce2e59db/gradio-6.9.0-py3-none-any.whl

Install Gradio Python Client from this PR

pip install "gradio-client @ git+https://github.com/gradio-app/gradio@ade077eed2857e48507ca3867735d9a5ce2e59db#subdirectory=client/python"

Install Gradio JS Client from this PR

npm install https://gradio-npm-previews.s3.amazonaws.com/ade077eed2857e48507ca3867735d9a5ce2e59db/gradio-client-2.1.0.tgz

@gradio-pr-bot
Copy link
Collaborator

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
@gradio/html minor
gradio minor

  • Add watch function to gr.HTML js_on_load

‼️ Changeset not approved. Ensure the version bump is appropriate for all packages before approving.

  • Maintainers can approve the changeset by checking this checkbox.

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

html_template: A string representing the HTML template for this component as a JS template string and Handlebars template. The `${value}` tag will be replaced with the `value` parameter, and all other tags will be filled in with the values from `props`. This element can have children when used in a `with gr.HTML(...):` context, and the children will be rendered to replace `@children` substring, which cannot be nested inside any HTML tags.
css_template: A string representing the CSS template for this component as a JS template string and Handlebars template. The CSS will be automatically scoped to this component, and rules outside a block will target the component's root element. The `${value}` tag will be replaced with the `value` parameter, and all other tags will be filled in with the values from `props`.
js_on_load: A string representing the JavaScript code that will be executed when the component is loaded. The `element` variable refers to the HTML element of this component, and can be used to access children such as `element.querySelector()`. The `trigger` function can be used to trigger events, such as `trigger('click')`. The value and other props can be edited through `props`, e.g. `props.value = "new value"` which will re-render the HTML template. If `server_functions` is provided, a `server` object is also available in `js_on_load`, where each function is accessible as an async method, e.g. `server.list_files(path).then(files => ...)` or `const files = await server.list_files(path)`. The `upload` async function can be used to upload a JavaScript `File` object to the Gradio server, returning a dictionary with `path` (the server-side file path) and `url` (the public URL to access the file), e.g. `const { path, url } = await upload(file)`.
js_on_load: A string representing the JavaScript code that will be executed when the component is loaded. The `element` variable refers to the HTML element of this component, and can be used to access children such as `element.querySelector()`. The `trigger` function can be used to trigger events, such as `trigger('click')`. The value and other props can be edited through `props`, e.g. `props.value = "new value"` which will re-render the HTML template. If `server_functions` is provided, a `server` object is also available in `js_on_load`, where each function is accessible as an async method, e.g. `server.list_files(path).then(files => ...)` or `const files = await server.list_files(path)`. The `upload` async function can be used to upload a JavaScript `File` object to the Gradio server, returning a dictionary with `path` (the server-side file path) and `url` (the public URL to access the file), e.g. `const { path, url } = await upload(file)`. The `watch` function can be used to observe prop changes: `watch('value', () => { ... })` runs the callback after the template re-renders whenever `value` changes, or `watch(['value', 'color'], () => { ... })` to watch multiple props.`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a stray backtick at the end of this line

@hannahblair
Copy link
Collaborator

image

handy demo @aliabid94!

@hannahblair
Copy link
Collaborator

nice one @aliabid94!

i did a bit of digging to better understand the component and one thing i found is that the max_flush_depth doesn't seem to be having an effect and flushDepth never actually exceeds 1 because it gets reset. running this demo demonstrates that and causes the browser to freeze:

import gradio as gr

with gr.Blocks() as demo:
    freeze_html = gr.HTML(
        value=0,
        html_template="""
        <div>
            <div>Value: ${value}</div>
            <button class="freeze">Click to freeze browser</button>
        </div>
        """,
        js_on_load="""
        element.querySelector('.freeze').addEventListener('click', () => {
            props.value = 1;
        });

        watch('value', () => {
            props.value++;  // infinite loop here
        });
        """,
    )

if __name__ == "__main__":
    demo.launch()

@aliabid94
Copy link
Collaborator Author

Ok, updated the watch method to only listen to updates from the backend. So watch is only triggered when the component is an output to an event listener.

I think this is the best approach, it allows for a lot more interactivity when a prop updates from the backend, which wasn't possible before. I had to draw the line of complexity at some point and trying to recreate svelte with full reactivity to props in the frontend was just getting way too messy, and I think this is a simple clean compromise.

If I can get a re-review @hannahblair that would be great!

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

Successfully merging this pull request may close these issues.

4 participants