Client runtime

All of @dconco/phpspa in one place.

These docs focus on the Vite + TypeScript runtime that boots when you import @dconco/phpspa. Every API here complements the PHP-first pages inside this starter.

Lifecycle

Listen to beforeload/load and hook analytics.

Navigation

Drive navigate(), history, reloads, and link interception.

State & effects

Share stores with setState, useEffect, and server callbacks.

Install

Bring the runtime into Vite.

The package depends on Vite (or any bundler) and executes automatically once imported. It registers itself on window.phpspa so you can inspect state during development.

pnpm add @dconco/phpspa | npm install @dconco/phpspa

// src/main.ts
import phpspa from '@dconco/phpspa';
import './style.css';

phpspa.on('load', ({ route, success }) => {
   console.debug('[phpspa]', route, success);
});

Events

Hook into navigation.

Both events replay the last payload for late listeners.

  • beforeload — fires before fetch. Show loaders, pause video, cancel requests.
  • load — fires after DOM diff. Inspect success, error, data.

Typical handler

const spinner = document.querySelector('#loader');

phpspa.on('beforeload', ({ route }) => {
   spinner?.classList.remove('hidden');
   console.time(route);
});

phpspa.on('load', ({ route, success, error }) => {
   spinner?.classList.add('hidden');
   console.timeEnd(route);
   if (!success) toast.error(error ?? 'Unknown error');
});

Navigation helpers

Programmatic control

  • phpspa.navigate(url, 'push'|'replace')
  • phpspa.back() / phpspa.forward()
  • phpspa.reload() — soft refresh current URL
  • phpspa.reloadComponent() — refresh the active target only

Declarative links

Anchor tags emitted by <Component.Link> include data-type="phpspa-link-tag". The runtime intercepts same-origin clicks, keeps scroll restoration, and falls back for target="_blank" or downloads.

Add the data attribute manually to any custom anchor if you want the same behavior.

State bridge

Named imports expose the same helpers as the global runtime.

import { setState, useEffect } from '@dconco/phpspa';

setState('counter', (prev: number) => prev + 1)
   .then(() => console.log('DOM updated!'));

useEffect(() => {
   const timer = setInterval(() => setState('ping', Date.now()), 4000);
   return () => clearInterval(timer);
}, ['ping']);

Rules of thumb

  • Effects run once on mount, then whenever one of the dependency keys is touched by setState.
  • setState replays the current routes with a signed payload so PHP can re-render each target.
  • Cleanup functions run before the next effect invocation and during navigation.
  • Global helpers also live on window.setState and window.useEffect for quick demos.

If the DOM you touch lives inside a PhpSPA target, it gets replaced on navigation, so your effect unmounts. Re-register your helper after each load event or move the logic into the static layout.

Stable callbacks

useCallback(fn, deps) returns a memoized version of fn whose identity stays stable across renders until any dependency changes. Use it when attaching event listeners, passing callbacks to child components, or anytime a stable reference prevents duplicate effects

import phpspa, { useCallback } from '@dconco/phpspa';

const handleToggle = useCallback((event: MouseEvent) => {
   event.preventDefault();
   document.querySelector('#nav-links')?.classList.toggle('open');
}, []);

phpspa.on('load', ({ success }) => {
   if (!success) return;
   document.querySelector('#nav-trigger')?.addEventListener('click', handleToggle, { passive: true });
});

TypeScript helpers

Import runtime types whenever you need strongly typed payloads, caches, or instance helpers.

import type { EventPayload, EventName, StateValueType, PhpSPAInstance } from '@dconco/phpspa';

declare const phpspaInstance: PhpSPAInstance;

const cache: Record<string, StateValueType> = {};

phpspaInstance.on('load', ({ route, success }: EventPayload) => {
   const eventName: EventName = 'load';

   cache[eventName] = { route, success } satisfies StateValueType;
   console.log(`[${eventName}] ${route}`, success);
});

Server calls

Use __call with useFunction.

PhpSPA emits signed tokens for each callable you expose from PHP. Grab them server-side via Component\useFunction('function_name')->token, pass the token plus any arguments to __call, and the helper handles encoding, Authorization headers, and JSON decoding.

import { __call } from '@dconco/phpspa';

async function toggle(id: string) {
   const result = await __call(window.token, id);
   if (result?.error) throw new Error(result.error);
}

// --- PHP Side ---
<?php
function Toggle($id) { ... }

$token = Component\useFunction('Toggle')->token;

$app = new PhpSPA\App();

$app->script(fn () => <<<JS
   window.token = $token;
JS);

Scripts & styles

  • Inline <script> blocks rerun in isolated scopes after each navigation.
  • <phpspa-script> (or script[data-type="phpspa/script"]) fetch once, cache contents, and respect CSP nonces.
  • Inline <style> tags dedupe via content hash before re-injection.

Debug tips

  • Pair beforeload/load with analytics to measure swap latency.
  • Use  code class=code-chip>setState(...).then() to wait for DOM updates before reading measurements.
  • Log beforeload/load payloads directly from the event bus to inspect routes without exposing globals.