Nitro

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:

JavaScript
// 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
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.

JavaScript
// 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.

JavaScript
// 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 afterRequest or afterNavigate, with cancelled: 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.

Next steps