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.
Client runtime
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
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
Both events replay the last payload for late listeners.
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 URLphpspa.reloadComponent() — refresh the active target onlyDeclarative 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
setState.setState replays the current routes with a signed payload so PHP can re-render each target.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
__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
<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.<style> tags dedupe via content hash before re-injection.Debug tips
beforeload/load with analytics to measure swap latency.beforeload/load payloads directly from the event bus to inspect routes without exposing globals.