Going further
Hooks
Hooks let your own JavaScript take part in what Nitro does in the browser: a request about to be sent, a visit finishing, a component leaving the page. Listen to the moments you need, add a header, count a page view, or stop a visit when there are unsaved changes.
On this page
A component's own hooks, such as mounted() and updated(), are about that one
component (see Lifecycle). The hooks on this page are the
runtime's: they run for every component, request and visit on the page, which is what analytics, error
trackers, progress bars and your own plugins need.
Listening
Nitro.hook(name, listener) calls the listener each time the moment comes, and returns a function
that removes it:
// Every request a component sends carries the current tenant.
Nitro.hook('beforeRequest', ({ headers }) => {
headers['X-Tenant'] = document.body.dataset.tenant;
});
// A page view after each visit that went through.
Nitro.hook('afterNavigate', ({ url, cancelled }) => {
if (!cancelled) plausible('pageview', { u: url });
});
// A hook() returns a function that removes the listener.
const stop = Nitro.hook('component.rendered', (component) => console.log(component.name, 'rendered'));
stop();
The hooks
The page
| Hook | When | The listener gets |
|---|---|---|
init
|
Once, before the page's components start. Register directives, view engines and plugins here |
{ Nitro }
|
initialized
|
Once, when the first page's components are all live |
{ Nitro }
|
Components
| Hook | When | The listener gets |
|---|---|---|
component.init
|
A component is made, before its first render | the component |
component.initialized
|
It has started in the page: its first render is done | the component |
component.rendered
|
After each of its renders | the component |
component.destroyed
|
It has left the page, whatever took it out: its view, a visit, or another script | the component |
A component is { name, el, dispatch(event, ...params) }. A listener of
component.initialized added after the page started hears the components already there too.
Stores
| Hook | When | The listener gets |
|---|---|---|
store.initialized
|
A store is made in this tab | the store |
store.updated
|
Its values changed: here, from the server, or from another tab with
#[Sync]
|
the store |
A store is { name, persist, sync, get(property), values(), set(property, value), onChange(listener) },
its values as the server sends them. set() is a change made in the browser, which the server checks
on the next call.
Requests
| Hook | When | The listener gets |
|---|---|---|
beforeRequest
|
Before a request goes: a
#[Server]
call (
kind
is
call
), a partial reload (
reload
), an upload (
upload
)
|
{ component, method, kind, headers }
. Add to
headers
to send your own; Nitro's can't be changed
|
afterRequest
|
Its answer arrived, or it was stopped |
{ component, method, kind, status, cancelled, ms, bytes }
, and in development
serverMs
and
dbMs
.
status
is 0 when no answer came
|
Navigation
| Hook | When | The listener gets |
|---|---|---|
beforeNavigate
|
A
nitro:navigate
visit starts, from a link,
Nitro.visit()
, or back and forward (
history
)
|
{ url, cancelable, history }
|
afterNavigate
|
The visit is over: its page is shown, or it was stopped, or a newer visit replaced it before its page came (
cancelled
)
|
{ url, cancelled, history }
|
Messages
| Hook | When | The listener gets |
|---|---|---|
flash
|
Flash data arrived for the page, as
Nitro::flash()
sent it
|
{ flash }
|
error
|
Something failed in the browser: a call, a render, a handler |
{ component, message }
, and
status
and
body
for a failed request
|
expired
|
The session or the component's state expired (419). Nitro reloads the page unless a listener calls
preventDefault()
|
{ component, status }
|
Stopping a request or a visit
A beforeRequest or beforeNavigate listener can call preventDefault() to
stop what is about to happen. A stopped #[Server] call sends nothing and resolves with
null; what changed in the browser stays, and goes with the next call. A stopped upload fires
nitro-upload-cancel on its input.
// Ask before leaving a page with changes the server hasn't seen yet.
Nitro.hook('beforeNavigate', (visit) => {
const unsaved = Nitro.components().some((component) => component.unsaved().length > 0);
if (unsaved && visit.cancelable && !confirm('You have unsaved changes. Leave this page?')) {
visit.preventDefault();
}
});
Back and forward can't be stopped: by the time the visit starts, the browser has already changed the address.
Their beforeNavigate has cancelable: false, and preventDefault() does
nothing there.
// Offline: hold #[Server] calls back and say so. What changed stays, and goes with the next call.
Nitro.hook('beforeRequest', (request) => {
if (request.kind === 'call' && !navigator.onLine) {
request.preventDefault();
showToast('You are offline. Your changes are kept and will be saved when you are back.');
}
});
- Every before has its after. A stopped request or visit still gets its
afterRequestorafterNavigate, withcancelled: true, so a counter of what's in flight, such as a progress bar, stays right. - The order. Listeners run in the order they were added. One added earlier hears a moment even when a later one stops it, which is why the after always comes.
Before Nitro is on the page
Nitro.hook() needs Nitro. A script that loads before @nitroScripts, such
as a module in the <head>, waits for nitro:init on the document instead, which
comes with Nitro just before any component starts. See
Running code before components start.
Window events
Most hooks also dispatch an event, with the same details in event.detail and the component by
its name. It suits code that only listens, such as a widget that knows nothing of Nitro. A stoppable hook's
event can be stopped with event.preventDefault() as well.
| Events | For | Earlier names, still sent |
|---|---|---|
nitro:init
,
nitro:initialized
|
On the document |
nitro:starting
for
nitro:init
|
nitro:before-request
,
nitro:after-request
|
Every request |
nitro:request
and
nitro:response
, for
#[Server]
calls
|
nitro:before-navigate
,
nitro:after-navigate
|
Every visit |
nitro:navigating
and
nitro:navigated
|
nitro:flash
,
nitro:error
,
nitro:expired
|
The earlier names are still sent through 2.x, so code written for them keeps working. They can't be stopped, as they never could.
In a plugin
A plugin's on(name, listener) is Nitro.hook(), so a plugin listens to the same hooks.
The names plugins used before, component, render, store,
request and response, still work. See
Writing plugins.
When a listener fails
A listener that throws is reported, as the error hook and in the console, and the other
listeners still run. A listener of error itself that throws only goes to the console, so a
broken error tracker can't loop.