-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Description
When using sveltify() to wrap React components in Svelte 5, prop changes are not reflected in the React component. This breaks the controlled component pattern commonly used in React.
Reproduction
<script>
import { sveltify } from "svelte-preprocess-react";
import { Accordion } from "some-react-library";
const react = sveltify({ Accordion });
let isOpened = $state(false);
</script>
<button onclick={() => isOpened = !isOpened}>
Toggle: {isOpened}
</button>
<react.Accordion
isOpened={isOpened}
onOpen={() => isOpened = true}
onClose={() => isOpened = false}
>
Content
</react.Accordion>Expected behavior
When clicking the button, isOpened changes and the React Accordion component should open/close accordingly.
Actual behavior
The button text updates (showing state is changing), but the React component does not update.
Environment
- svelte-preprocess-react: 3.0.0-beta.0
- Svelte: 5.45.6
- React: 19.2.3
Root Cause Analysis
I investigated and found two issues in SveltifiedCSR.svelte:
1. Reactivity loss from rest spread on $props()
// Current code
const { react$component, react$children, children, ...props } = $props();In Svelte 5, using rest spread (...props) on $props() creates a plain object, breaking fine-grained reactivity tracking. The $effect does not re-run when individual props change.
2. React batching conflict with Svelte's $effect
Even after fixing reactivity, React's internal batching mechanism skips re-renders when app.render() is called from within Svelte's $effect (which runs in a microtask).
Suggested Fix
// 1. Keep $props() as whole object
const allProps = $props<Record<string, any>>();
$effect(() => {
if (!target) return;
// 2. Force read all properties for dependency tracking
const _trigger = JSON.stringify(allProps);
const { react$component, react$children, children: _children, ...props } = allProps;
// 3. Use flushSync to force synchronous React rendering
flushSync(() => {
app.render(/* ... */);
});
});
// 4. Update template references
{#if allProps.children}
{@render allProps.children()}
{/if}I tested this fix locally and controlled component pattern now works correctly.