Build with confidence
Migration cheatsheet
Side-by-side translations from React / Solid / Vue / Svelte to Purity.
Not exhaustive — covers the operations you reach for daily.
Reactive state
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Create |
useState(0) |
createSignal(0) |
ref(0) |
let n = $state(0) |
state(0) |
| Read |
n |
n() |
n.value |
n |
n() or n.get() |
| Write |
setN(5) |
setN(5) |
n.value = 5 |
n = 5 |
n(5) or n.set(5) |
| Updater |
setN((v) => v + 1) |
setN((v) => v + 1) |
n.value++ |
n++ |
n((v) => v + 1) |
| Untracked read |
n/a |
untrack(() => n()) |
n/a |
n/a |
n.peek() |
Derived values
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Computed |
useMemo(() => n * 2, [n]) |
createMemo(() => n() * 2) |
computed(() => n.value * 2) |
let d = $derived(n * 2) |
compute(() => n() * 2) |
Effects / watchers
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Auto-tracked side effect |
useEffect(() => …, [n]) |
createEffect(() => …) |
watchEffect(() => …) |
$effect(() => …) |
watch(() => …) |
| Specific source |
n/a |
createEffect(on(n, …)) |
watch(n, (next, prev) => …) |
n/a |
watch(n, (next, prev) => …) |
| Cleanup |
return fn from effect |
onCleanup(() => …) |
inside watchEffect |
inside $effect |
return fn from watch body, or onDispose(fn) |
Async data
| Concept |
React (RTK / TanStack) |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Reactive resource |
useQuery({ key, fn }) |
createResource(src, fn) |
userland (<Suspense>) |
{#await} (template only) |
resource(src, fn) |
| Trigger imperatively |
useMutation(fn).mutate() |
n/a built-in |
userland |
await fn() |
lazyResource(fn).fetch(args) |
| Optimistic update |
setQueryData(key, v) |
mutate(v) |
userland |
userland |
r.mutate(v) |
| Manual refetch |
refetch() |
refetch() |
userland |
re-render |
r.refresh() |
| Loading flag |
isLoading |
r.loading |
userland |
{:then} block |
r.loading() |
| Error |
error |
r.error |
userland |
{:catch} block |
r.error() |
| Built-in retry |
TanStack retry: 3 |
userland |
userland |
userland |
{ retry: 3 } option |
| Built-in polling |
TanStack refetchInterval |
userland |
userland |
userland |
{ pollInterval: ms } option |
| Built-in debounce |
userland |
userland |
userland |
userland |
debounced(src, ms) |
Lists
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Render |
items.map(i => <li key={i.id}>{i.name}</li>) |
<For each={items()}>{i => <li>{i.name}</li>}</For> |
v-for="i in items" |
{#each items as i (i.id)} |
each(() => items(), (i) => html\<li>${() => i().name}</li>`, (i) => i.id)` |
Conditionals
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| If/else |
cond ? <A/> : <B/> |
<Show when={cond()}> |
v-if |
{#if cond} |
when(() => cond(), () => html\A`, () => html`B`)` |
| Switch |
switch (...) |
<Switch> |
v-if chain |
{#if … :else if} |
match(() => key(), { a: …, b: … }) |
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Text input |
<input value={v} onChange={e => set(e.target.value)} /> |
<input value={v()} onInput={e => set(e.target.value)} /> |
v-model="v" |
bind:value={v} |
<input ::value=${v} /> |
| Checkbox |
<input checked={c} onChange={…} /> |
<input checked={c()} onChange={…} /> |
v-model="c" |
bind:checked={c} |
<input type="checkbox" ::checked=${c} /> |
Lifecycle
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| After mount |
useEffect(() => …, []) |
onMount(() => …) |
onMounted(() => …) |
$effect(() => …) (initial run) |
onMount(() => …) |
| Before unmount |
cleanup from effect |
onCleanup(() => …) |
onBeforeUnmount(() => …) |
$effect cleanup |
onDestroy(() => …) |
| Generic disposer |
n/a |
onCleanup(fn) |
onScopeDispose(fn) |
n/a |
onDispose(fn) |
| Error boundary |
<ErrorBoundary> |
<ErrorBoundary> |
errorCaptured |
<svelte:boundary> |
onError((err) => …) |
Components
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Define |
function Card({ title }) { … } |
function Card(props) { … } |
defineComponent({ props, setup }) |
<script>let { title } = $props();</script> |
component<{ title: string }>('p-card', ({ title }) => …) |
| Use in template |
<Card title="x" /> |
<Card title="x" /> |
<Card title="x" /> |
<Card title="x" /> |
<p-card :title=${'x'}></p-card> |
| Default slot |
{children} |
{props.children} |
<slot> |
{@render children?.()} |
slot() accessor |
| Named slot |
render-prop or context |
<slot name="header"> |
<slot name="header"> |
{@render header?.()} |
slot('header') accessor |
| Scoped slot |
render-prop |
<slot data={...}> |
v-slot="{ data }" |
{#snippet header(data)} |
slot<{ user: User }>('header') accessor |
DOM refs
| Concept |
React |
SolidJS |
Vue 3 |
Svelte 5 |
Purity |
| Element ref |
useRef() + ref={…} |
let el; <div ref={el}> |
ref(null) + :ref="el" |
bind:this={el} |
No built-in ref — query inside onMount (e.g. onMount(() => { const el = root.querySelector('.target'); … })). A first-class ref API is post-1.0. |
Templates
Purity uses html\`tagged template literals (compiled by the@purityjs/vite-plugin`):
| Need |
Syntax |
Example |
| Reactive text |
${() => signal()} |
<p>${() => count()}</p> |
| Event listener |
@event=${fn} |
<button @click=${save}> |
| One-way prop |
:prop=${value} |
<p-card :title=${title}> |
| Two-way |
::prop=${signal} |
<input ::value=${name} /> |
| Boolean attr |
?attr=${bool} |
<button ?disabled=${() => !valid()}> |
| DOM property |
.prop=${value} |
<input .checked=${flag}> |
Check the differences before migrating
- Rendering: Purity supports SSR and hydration through
@purityjs/ssr and hydrate(). The CLI's --ssr starter supplies a Node server; server rendering covers the path from project creation to deployment. Islands allow selected regions to hydrate.
- Routing:
@purityjs/core exports route and navigation primitives, including currentPath(), matchRoute(), and navigate(). Review their composition before replacing an app that relies on a framework-specific router.
- State: Share
state() from a module for app-wide values. Use resource() or query() when asynchronous data needs loading and error states.
- Styling and templates: Purity uses
html tagged templates rather than JSX. Use css tagged templates inside components and regular stylesheets elsewhere. The Vite plugin compiles templates ahead of time.
Edit this page on GitHub