Skip to content

React Adapter

@weldjs/react bridges WELD signals to React using useSyncExternalStore — the official React 18 API for external stores. It works correctly with Concurrent Mode, Suspense, and Strict Mode.

Terminal window
npm install @weldjs/react zod
import { useWeld } from '@weldjs/react'
// Overload 1 — stable reference (module-level or useMemo)
useWeld(response: WeldResponse<T>): UseWeldResult<T>
// Overload 2 — factory with deps (Strict Mode safe, recommended)
useWeld(factory: () => WeldResponse<T>, deps: unknown[]): UseWeldResult<T>
Field Type Description
data T | null The validated response data
status 'idle' | 'loading' | 'success' | 'error' Current request status
error Error | null Error if the request failed
loading boolean Shorthand for status === 'loading'

How to call useWeld — 3 patterns, all work

Section titled “How to call useWeld — 3 patterns, all work”

Pattern 1 — Module-level response (simplest, always safe)

Section titled “Pattern 1 — Module-level response (simplest, always safe)”

Create the request outside the component. This is the most explicit pattern and has zero edge cases.

import { Weld } from '@weldjs/http'
import { useWeld } from '@weldjs/react'
import { z } from 'zod'
const api = new Weld('https://api.example.com')
const ProductSchema = z.object({
id: z.string(),
name: z.string(),
price: z.number(),
})
// ✅ Created once at module level — stable across all renders
const productsRequest = api.get('v1/products', z.array(ProductSchema))
export function ProductList() {
const { data, loading, error } = useWeld(productsRequest)
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
return (
<ul>
{data?.map(p => <li key={p.id}>{p.name} — ${p.price}</li>)}
</ul>
)
}
Section titled “Pattern 2 — Factory with deps (recommended for dynamic requests)”

Pass a factory function and a deps array — exactly like useEffect. The response is created once per dep change, and is Strict Mode safe.

export function UserDetail({ id }: { id: string }) {
const { data, loading } = useWeld(
() => api.get(`users/${id}`, UserSchema),
[id] // recreate when id changes
)
if (loading) return <p>Loading user...</p>
return <div>{data?.name}</div>
}

Pattern 3 — useMemo (explicit, also works)

Section titled “Pattern 3 — useMemo (explicit, also works)”
import { useMemo } from 'react'
export function ProductList({ category }: { category: string }) {
const request = useMemo(
() => api.get('v1/products', z.array(ProductSchema), { query: { category } }),
[category]
)
const { data, loading } = useWeld(request)
// ...
}

Mutations don’t need useWeld — call them directly in event handlers:

export function CreateProduct() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
await api.post('v1/products', ProductSchema, {
body: { name: 'Widget', price: 9.99 },
}).promise
}
return <form onSubmit={handleSubmit}>...</form>
}

Inside useEffect you have full lifecycle control — direct api.get() is fine here:

import { useEffect } from 'react'
export function ProductList() {
useEffect(() => {
// ✅ Direct api.get() inside useEffect is fine — you control the lifecycle
const { promise, abort } = api.get('v1/products', z.array(ProductSchema))
promise.then(console.log).catch(console.error)
return () => abort() // cancel on unmount
}, [])
}