Skip to content

fix(compiler): make early-return guards reactive in component bodies#2991

Merged
viniciusdacal merged 1 commit intomainfrom
fix/early-return-reactivity
Apr 23, 2026
Merged

fix(compiler): make early-return guards reactive in component bodies#2991
viniciusdacal merged 1 commit intomainfrom
fix/early-return-reactivity

Conversation

@viniciusdacal
Copy link
Copy Markdown
Contributor

Summary

  • Detects the if (cond) return <jsx>; ... return <jsx>; guard shape in component bodies and rewrites the body so the main return is wrapped in a reactive __conditional(() => cond, () => branch, () => fallback) chain.
  • Closes Compiler: early returns in components are not reactive #2987 — components like if (data.loading) return <Loading/>; return <Ready/>; now swap to the ready branch when data.loading flips, instead of staying frozen at the guard forever.
  • Non-guard shapes (else branches, multi-statement blocks, intermediate statements between guards) are left alone so the per-return mount-frame wrapper keeps handling them.

Public API Changes

No new or removed APIs. Behavior change only: the compiler's output structure for early-return guard components now uses __conditional so conditions are tracked reactively. This matches the documented loading-UX pattern.

Test plan

  • Rust unit tests in native/vertz-compiler-core/src/reactive_guard_transformer.rs cover simple/multi/braced guards, else branches (rejected), non-guard ifs (rejected), and non-guard components (unchanged).
  • JS compiler tests in native/vertz-compiler/__tests__/reactive-guards.test.ts assert the emitted code has __conditional(() => cond, ...) and a single __flushMountFrame().
  • Runtime regression in packages/ui/src/__tests__/reactive-guard-runtime.test.ts mounts the compiler-emitted structure and verifies DOM swaps both directions, including nested guards.
  • Updated two older mount-frame tests that used the now-reactive shape to intentionally non-guard shapes (multi-statement block, intermediate statement) so they still exercise per-return mount-frame wrapping.
  • Full quality gates green on push (rust clippy/fmt/test, TS typecheck/lint/test, trojan-source check).

🤖 Generated with Claude Code

…2987]

A component of the shape `if (cond) return <Loading/>; return <Ready/>;` stayed
frozen at the guard branch because the body ran once at mount and never re-ran
when `cond` flipped. This broke the documented loading-UX pattern
(`query().loading` + early return).

The compiler now detects the guard shape — N consecutive
`if (cond) return <jsx>;` at the top of a component body, followed by a single
trailing `return <jsx>;` — and rewrites the body to wrap the main return in a
chain of `__conditional(() => cond, () => branch, () => fallback)` calls. The
condition is re-evaluated reactively and the DOM swaps between branches
without re-mounting the component. Guards with `else`, multi-statement blocks,
or non-guard statements between guards are left alone for the per-return
mount-frame wrapper to handle.

Closes #2987.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@viniciusdacal viniciusdacal merged commit 27ea038 into main Apr 23, 2026
7 checks passed
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.

Compiler: early returns in components are not reactive

1 participant