Skip to content

Reduce gr.State boilerplate in medium-complexity Blocks apps #13206

@liuxin99

Description

@liuxin99

Feature Request: Reduce gr.State boilerplate in medium-complexity Blocks apps

Summary

gr.State works well for simple demos, but in medium-complexity multi-step applications, state handling becomes repetitive and hard to maintain.

The main issue is that event handlers must manually thread state through inputs and outputs, which creates a lot of boilerplate and makes refactors fragile.

Real-world use case

A TTS application can quickly grow beyond a single "text in, audio out" demo.

For example, a multi-step TTS dubbing workflow may include:

  1. Upload and parse a script into lines
  2. Assign voice presets to roles
  3. Synthesize line by line
  4. Re-generate specific lines with adjusted parameters
  5. Track progress and preview generated clips
  6. Concatenate and export the final audio

Even for this fairly normal AI app, state may include:

script_lines = gr.State([])
role_voice_map = gr.State({})
current_idx = gr.State(0)
audio_clips = gr.State({})
tts_params = gr.State({"speed": 1.0, "pitch": 0, "cfg": 2.0})
undo_stack = gr.State([])
export_config = gr.State({"format": "wav", "normalize": True})

Pain points

  1. Repetitive state plumbing in callbacks
    Callbacks need to explicitly pass every relevant state object:
synth_btn.click(
    fn=synthesize_current,
    inputs=[
        script_lines,
        current_idx,
        role_voice_map,
        tts_params,
        speed_slider,
        pitch_slider,
        audio_clips,
        undo_stack,
    ],
    outputs=[audio_clips, undo_stack, audio_preview, progress_text],
)

This becomes hard to maintain as features grow. Adding one new state field often requires updating many event bindings.

  1. Cleanup/reset logic is verbose and fragile
    When upstream input changes, downstream state often needs to be reset manually.
    For example, if the user uploads a new script, old generated audio, progress, and undo history may all need to be cleared. Today this often requires extra callbacks or .then(...) chains.

  2. Derived values must be manually recomputed
    Simple derived values such as progress text:

f"{len(audio_clips)}/{len(script_lines)} synthesized"

need to be recalculated inside every callback that touches related state, instead of being defined once.

  1. Event chains become hard to reason about
    As more components are linked together, application logic can become a long sequence of callback wiring and .then(...) chains. This is workable, but becomes harder to read and refactor over time.

Note on existing features
@gr.render helps with dynamic UI generation, which is useful. However, it does not fully solve the state-handling problem above, because state mutation and state propagation across callbacks are still manual.

Possible improvement directions
Any lightweight improvement here would help a lot. For example:
A simpler way to update state without always listing full inputs / outputs
A helper for grouped or named state objects
Built-in support for resetting related state
A lightweight notion of derived/computed state
More declarative patterns for common multi-step workflows

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions