Skip to content

Composables

Composables are plain functions that encapsulate reusable logic. They live in src/composables/ and are called inside page or component functions.


useRoute()

Returns reactive accessors for the current route state.

js
import { useRoute } from '../composables/useRoute.js'

function MyPage() {
  const route = useRoute()

  return html`
    <p>Path: ${() => route.path()}</p>
    <p>User ID: ${() => route.params().id}</p>
  `
}
AccessorReturns
route.path()Current URL path string
route.params()Object of dynamic route params
route.status()Router status: 'idle' · 'loading' · 'ready' · 'not-found' · 'error'
route.meta()The meta object exported by the current page

Each accessor is a function — always call it inside a ${} slot so Arrow.js can track it reactively.


useRouter()

Returns navigation methods.

js
import { useRouter } from '../composables/useRouter.js'

function MyPage() {
  const router = useRouter()

  return html`
    <button @click="${() => router.go('/users')}">Users</button>
    <button @click="${() => router.back()}">Back</button>
    <button @click="${() => router.forward()}">Forward</button>
  `
}
MethodDescription
router.go(path)Navigate to a path; returns a Promise that resolves when navigation completes
router.back()Navigate back in history
router.forward()Navigate forward in history

Uses the browser's Navigation API internally.


useToast()

Triggers global toast notifications backed by the reactive toastState singleton. Call it inside any page or component.

js
import { useToast } from '../composables/useToast.js'

function MyPage() {
  const toast = useToast()

  return html`
    <button @click="${() => toast.success('Changes saved.')}">Save</button>
    <button @click="${() => toast.error('Something went wrong.', { duration: 0 })}">Error</button>
  `
}

Four type shortcuts — success, error, warning, info — each accept an optional opts object with duration (ms, 0 = permanent) and dismissible (show close button). Call toast.dismiss(id) to remove a toast immediately. Adjust global defaults with toastState.configure({ position, duration, dismissible }).


useForm(initialValues, options)

Manages form state, validation, and submission. Must be called inside a page function — not at module scope — so state resets on each navigation.

js
import { useForm } from '../composables/useForm.js'

function LoginPage() {
  const { form, handleSubmit, field } = useForm(
    { email: '', password: '' },
    {
      validate(values) {
        const errors = {}
        if (!values.email) errors.email = 'Email is required.'
        return errors
      },
      async onSubmit(values, form) {
        await loginUser(values)
        form.message = `Signed in as ${values.email}`
      },
    }
  )

  const emailField = field('email')

  return html`
    <form @submit="${handleSubmit}">
      <input type="email" @input="${emailField.set}" />
      ${() => emailField.error() ? html`<p>${() => emailField.error()}</p>` : ''}
      <button
        type="submit"
        aria-disabled="${() => form.submitting ? 'true' : 'false'}"
        class="${() => form.submitting ? 'opacity-50 cursor-not-allowed' : ''}"
      >${() => form.submitting ? 'Signing in…' : 'Sign in'}</button>
    </form>
    <p>${() => form.message}</p>
  `
}

useForm returns:

ValueDescription
formReactive state object (see below)
handleSubmitEvent handler — attach to @submit on the form element
field(name)Returns { get, set, error } accessors for a named field

form properties:

PropertyTypeDescription
form.valuesobjectCurrent field values
form.errorsobjectValidation errors keyed by field name
form.submittingbooleantrue while onSubmit is running
form.submittedbooleantrue after a successful submission
form.messagestringSet inside onSubmit to display a status message

field(name) accessors:

AccessorUsage
field.get()Reactive getter — use in ${} slots
field.setInput event handler — attach to @input
field.error()Reactive error string or undefined

useFetch(url, options?)

Fetches a JSON endpoint and returns reactive accessors for data, loading, error, and status. Cancels any in-flight request when refetch() is called or the component unmounts.

Must be called inside a page or component() factory — not at module scope.

js
import { useFetch } from '../composables/useFetch.js'

function PostsPage() {
  const posts = useFetch('https://jsonplaceholder.typicode.com/posts', {
    transform: (data) => data.slice(0, 10),
  })

  return html`
    ${() => posts.loading() ? html`<p>Loading…</p>` : ''}
    ${() => posts.error()   ? html`<p>Error: ${() => posts.error()}</p>` : ''}
    ${() => (posts.data() ?? []).map((post) =>
      html`<article><h2>${() => post.title}</h2></article>`.key(post.id)
    )}
    <button @click="${() => posts.refetch()}">Refresh</button>
  `
}
Return valueTypeDescription
data()() => any | nullParsed JSON response (or transformed value)
loading()() => booleantrue while the request is in flight
error()() => string | nullError message, or null on success
status()() => number | nullHTTP status code of the last response
refetch()() => Promise<void>Re-triggers the fetch; cancels any in-flight request first
OptionTypeDefaultDescription
immediatebooleantrueFetch on mount; set false for manual trigger
transform(data) => anyidentityApplied to the parsed JSON before storing
methodstring'GET'HTTP method
headersobject{}Request headers
bodystringRequest body

provide / inject

App-level dependency injection for passing configuration or services down to any layout or component without prop-drilling.

Provide in src/main.js:

js
import { provide } from './framework/index.js'

provide('app', { name: 'My App', tagline: 'Built with Quiver' })

Inject inside a layout or component function:

js
import { inject } from '../framework/index.js'

export function MenuLayout(content) {
  const app = inject('app', { name: 'Quiver', tagline: '' })
  // use app.name, app.tagline ...
}

inject must be called inside the function body, not at module scope — calling it at the top level of a file creates a circular import dependency.

The second argument to inject is the fallback value used when no matching provide call exists.