Nitro

Pages

Page data

A page loads its data from its route, and Nitro gives you tools to shape that. You can load slow data after the page shows, reload some properties, merge and scroll lists, share data with every component, and flash data once.

On this page

A page loads its data in mount(), on the server, from its route. The tools on this page control what to load later, what to load again, and how new data combines with what the page already has. Shared data and flash data work in components too.

Flash data from a server method live, in this page View source ↓

Deferring slow properties

A page can show first and load its slow data right after:

PHP Server
public ?array $stats = null;

public function mount(): void
{
    $this->defer('stats', fn () => Order::stats());
}
Blade
@deferred('stats')
    <x-stats :stats="$stats" />
@placeholder
    <p>Loading…</p>
@enddeferred

Nitro renders the page without the deferred properties, and the page then asks its own URL for them. The page stays usable while they load, and anything that changes in the meantime is kept. Use @deferred without names to wait for every deferred property.

Reloading properties

A page can reload some of its properties from its route. Call $this->reload('orders') in a method or a #[Server] method, or $reload('orders') in a handler. The page asks its URL again and takes the named properties from the answer, while the other properties keep their values in the browser. Without names, every property is reloaded, including what the visitor typed.

A reload runs mount() again. Use $this->wants() to skip the work a reload doesn't need:

PHP Server
#[Server]
public function add(string $item): void
{
    Order::create(['item' => $item]);
    $this->reload('orders', 'total');
}

public function mount(): void
{
    if ($this->wants('orders')) {
        $this->orders = Order::latest()->get()->toArray();
    }

    if ($this->wants('total')) {
        $this->total = Order::sum('amount');
    }
}

The answer signs the reloaded properties, so #[Locked] properties reach the next #[Server] call exactly as the server gave them.

Merging and scrolling lists

When a property is reloaded, you can merge the new value into the one the page already has, instead of replacing it. The first render and a visit still set it as usual.

PHP Server
public array $posts = [];
public array $tags = [];
public array $filters = [];

public function mount(): void
{
    $this->scroll('posts', fn () => Post::latest()->paginate(20))->matchOn('id');
    $this->merge('tags', fn () => Tag::popular()->get()->toArray())->matchOn('id');
    $this->deepMerge('filters', fn () => $this->defaultFilters());
}
Does
$this->merge() Appends the new items. ->prepend() puts them first, and ->append('data') merges the list at a key
$this->deepMerge() Merges arrays key by key, all the way down. Lists are appended
$this->scroll() Takes a paginator ( paginate() , simplePaginate() or cursorPaginate() ), and the property holds its items. $loadMore('posts') asks for the next page and appends it, and $this->hasMore('posts') tells you whether there is one
->matchOn('id') Replaces an item that has the same id, instead of adding it again
Blade
@foreach ($posts as $post)
    <article nitro:key="{{ $post['id'] }}">...</article>
@endforeach

@if ($this->hasMore('posts'))
    <div nitro:visible="$loadMore('posts')">Loading…</div>
@endif

Each closure runs only when the request wants its property. The merge rule travels with the signed reload, so the next #[Server] call merges the same way, and a #[Locked] list keeps what was loaded.

Scroll on the server, filter in the browser

Use scroll properties for long lists that the server pages through. For a list of a few hundred rows, send it whole and filter it in the browser with a #[Computed] method. Filtering then needs no request.

Sharing data with every component

Nitro::share() gives every component and page the same data. Call it in a middleware, so it runs on every request:

app/Http/Middleware/ShareNitroData.php Server
use Nitro\Facades\Nitro;

Nitro::share('user', fn () => $request->user()?->only('id', 'name'));
Nitro::share('locale', app()->getLocale());

Nitro::shareOnce('countries', fn () => Country::pluck('name', 'code'));
Nitro::share('permissions', Nitro::once(fn () => $user->permissions())->until(3600));
Blade
<span>{{ $this->shared('user.name', 'Guest') }}</span>
  • Read shared data with $this->shared(), in views and in methods. It takes dot notation and a default.
  • The data is sent with the page, with each visit and with each #[Server] answer. Closures are called when the data is read.
  • Use shareOnce() and Nitro::once() for data that rarely changes. The browser keeps it across visits and calls, and the server doesn't send it again until it expires or you send it with ->fresh(). Set the expiry with ->until(), as seconds, an interval or a date. Once data is shared at the top level, under a key without dots.

Everything shared is public

Shared data is sent to the browser, so never share anything secret.

Flashing data

Nitro::flash() sends data with the next response only, such as a message after a save. The example above flashes a message from its #[Server] method, and its view shows the message with $this->flashed().

Browser + server
<?php

namespace App\Nitro\Components;

use Nitro\Attributes\Server;
use Nitro\Component;
use Nitro\Facades\Nitro;

class Flash extends Component
{
    #[Server]
    public function save(): void
    {
        Nitro::flash('toast', 'Saved on the server at '.now()->format('H:i:s'));
    }
}

Flash data goes with this request's page, visit or #[Server] answer. After a redirect, it goes with the page the redirect leads to. After that it is gone, and the back button doesn't bring it back. The browser also emits a nitro:flash event with the data in event.detail.flash. Flash data needs the session, so the request must run through the web middleware.

Next steps