Skip to content

Fix async child rendering in custom components - #13817

Draft
abidlabs wants to merge 5 commits into
mainfrom
fix/issue-13131-custom-components-blocked-when-using-dynamic-chi
Draft

Fix async child rendering in custom components#13817
abidlabs wants to merge 5 commits into
mainfrom
fix/issue-13131-custom-components-blocked-when-using-dynamic-chi

Conversation

@abidlabs

@abidlabs abidlabs commented Sep 2, 2026

Copy link
Copy Markdown
Member

Description

Fixes two custom-component regressions caused by Gradio and a custom component using separate Svelte runtimes:

  • Custom component children rendered after an asynchronous Svelte boundary failed because the host-runtime children snippet was invoked from the custom runtime without an active host effect.
  • Reading Gradio's reactive prop proxies while the foreign runtime mounted registered those reads against the host $effect, causing the whole custom component to unmount and mount again on every prop update.

This change recreates the outer child snippet with the custom component's runtime and mounts a small host-runtime wrapper inside it. It also calls the foreign runtime's mount inside the host runtime's untrack, exports createRawSnippet from newly built custom component runtime bundles, and adds isolated-runtime regression coverage for both behaviors.

Closes: #13131
Closes: #13818

Reproduction

For #13131, rendering children after an async boundary failed:

{#await promise then value}
	<div>
		{value}
		{@render children?.()}
	</div>
{/await}

Before this change, the delayed block throws Cannot read properties of null (reading 'nodes') and never renders its child. After this change, both the resolved value and child render.

For #13818, a streaming update to a custom component caused its Svelte component to unmount and mount again for every yielded value. After this change, the component remains mounted while its value updates.

Testing

bash scripts/format_frontend.sh formatted the changes successfully. Its repository-wide svelte-check phase currently reports unrelated existing errors in PrismJS imports, preview Rollup types, and ColorPicker.

AI Disclosure

  • I used AI to reproduce and diagnose the issues, draft the implementation and tests, and prepare this PR description. I reviewed every changed line and ran the checks listed above.
  • I did not use AI

🎯 PRs Should Target Issues

This PR targets and closes #13131 and #13818. I checked both issues for overlapping open PRs before adding their fixes and found none.

Testing and Formatting Your Code

The frontend formatting and verification details are listed above. No backend code was changed.

@gradio-pr-bot

gradio-pr-bot commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

🪼 branch checks and previews

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

Install Gradio from this PR

pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/7bc0fe3782c1a60a21080516de08437b4a5deda8/gradio-6.26.0-py3-none-any.whl

Install Gradio Python Client from this PR

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

Import Gradio JS Client from this PR via CDN

import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/7bc0fe3782c1a60a21080516de08437b4a5deda8/browser.js";

@gradio-pr-bot

Copy link
Copy Markdown
Collaborator

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
@gradio/core patch
@gradio/preview patch
gradio patch

  • Fix async child rendering in custom components

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.

@abidlabs

abidlabs commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Verified this fix with paired public Spaces using byte-identical app.py and custom-component assets. The only runtime difference is the Gradio package: the before Space uses the latest release, and the after Space installs this PR's preview wheel.

Demo Result Link
Before Reproduces the regression: the heading renders, but neither hello nor the child button appears. gradio-13131-before
After Fixed: the delayed hello text and Child button both render. gradio-13131-after

Preview wheel used by the after Space:
https://huggingface.co/buckets/gradio/pypi-previews/resolve/1266bbab79e0633036abf9f7f868db6fa295765d/gradio-6.26.0-py3-none-any.whl

@abidlabs
abidlabs requested review from dawoodkhan82 and hysts and a balanced review from Copilot September 2, 2026 22:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The bridge alters child DOM structure, and the regression test does not exercise separate Svelte runtimes.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Fixes delayed child rendering across host and custom-component Svelte runtimes.

Changes:

  • Bridges host children into the custom runtime.
  • Exports createRawSnippet from generated runtimes.
  • Adds an asynchronous rendering regression test.
File summaries
File Description
js/preview/src/svelte_runtime_entry.js Exports the snippet factory.
js/core/src/MountCustomComponent.svelte Adds the cross-runtime child bridge.
js/core/src/MountChildren.svelte Renders host-runtime children.
js/core/src/AsyncChildren.test.svelte Provides an asynchronous test component.
js/core/src/MountCustomComponent.test.ts Tests delayed child rendering.
.changeset/tidy-symbols-act.md Records patch releases.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread js/core/src/MountCustomComponent.svelte Outdated
const runtime_children =
children && _runtime.createRawSnippet
? _runtime.createRawSnippet(() => ({
render: () => "<span></span>",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in ed50099. The foreign-runtime raw snippet now creates only a temporary hidden marker; after the host runtime mounts into it, target.replaceWith(...target.childNodes) removes that marker synchronously and leaves the real children as direct siblings at the render position. The regression test also asserts the button is a direct child of the async block.

Comment on lines +29 to +32
runtime: Promise.resolve({
createRawSnippet: runtime_create_raw_snippet,
mount,
unmount

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in ed50099. The fixture now loads its component and Svelte exports in an isolated iframe module graph, so it has independent runtime singleton state from the host test. I also validated the inverse: bypassing the bridge makes this test fail with the original Cannot read properties of null (reading nodes) stack; restoring the bridge passes.

@abidlabs

abidlabs commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

#13818 before/after verification

Both Spaces run the same app and byte-identical custom-component wheel. Click Stream updates and compare the visible mount counter:

  • Before: the output reaches value-4, but the component remounts repeatedly (Mount count: 16).
  • After: the output reaches value-4 while the component stays mounted (Mount count: 1).

@iwr-redmond

Copy link
Copy Markdown

@abidlabs you may wish to test the PR against the detailed reproduction provided by @Col0ring here.

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.

Custom components are remounted on every prop update (Gradio ≥ 6.20) [Custom Components] Blocked when using dynamic children for rendering

4 participants