Essentials
Properties
A component's public properties are its state. The server renders them, the browser keeps them, and your methods change them, wherever those methods run.
On this page
Every public property of a component is part of its state. The server renders the first page with it, and
the browser then keeps it. Your methods change it, most of them in the browser and the
#[Server] ones on the server. There is nothing to wire up. You declare a property and use it in
the view.
The component
The counter above has two properties. increment() changes $count in the browser,
without a request. Its #[Validate] rule is checked on the server before save()
runs, so a count sent from outside the page can't be negative or huge. $savedAt is marked
#[Locked], so the browser shows it but only save() on the server can set it. Open
Compiled to see what the browser runs.
<?php
namespace App\Nitro\Components;
use Nitro\Attributes\Locked;
use Nitro\Attributes\Server;
use Nitro\Attributes\Validate;
use Nitro\Component;
class Counter extends Component
{
#[Validate('integer|min:0|max:1000')]
public int $count = 0;
#[Locked]
public string $savedAt = '';
public function mount(): void
{
$this->count = session('counter.count', 0);
$this->savedAt = session('counter.saved_at', '');
}
public function increment(): void
{
$this->count++;
}
#[Server]
public function save(): void
{
$this->savedAt = now()->format('H:i:s');
// This demo keeps the count in the session. Your app would save it to the database.
session(['counter.count' => $this->count, 'counter.saved_at' => $this->savedAt]);
}
}
<div class="flex flex-wrap items-center gap-3">
<button nitro:click="increment" class="rounded-lg bg-slate-900 px-3.5 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200">
Count: {{ $count }}
</button>
<button nitro:click="save" nitro:loading.attr="disabled" class="rounded-lg px-3.5 py-2 text-sm font-semibold text-slate-700 ring-1 ring-slate-300 hover:bg-slate-50 disabled:opacity-50 dark:text-slate-200 dark:ring-white/15 dark:hover:bg-white/5">
Save on the server
</button>
<span class="text-sm text-slate-500">{{ $savedAt === '' ? 'Not saved yet' : 'Saved at '.$savedAt.'. Reload the page and it stays.' }}</span>
@error('count')
<span class="text-sm text-red-600 dark:text-red-400">{{ $message }}</span>
@enderror
</div>
Compiling…
This demo saves to the session
So that the example works for every visitor, without an account or a database, save() keeps the count in your session. Your application would save it to the database in the same way. The call to the server, the validation and the new state that comes back all stay the same.
Property types
A property can hold null, a bool, an int, a float, a
string, an array, a backed enum, an Eloquent model or collection, or an upload. A
typed property always keeps its type. If the browser sends a value of another type, the server refuses it
rather than converting it.
| Type | How it behaves |
|---|---|
bool, int, float, string
|
The browser can only set it to a value of the same type (an int is accepted for a float). |
array
|
A value, as in PHP: a copy never changes the property. Keys and their order survive the round trip. |
Backed enum
|
The browser holds the case as its value, and can only send the value of one of its cases. The enum must be marked #[Browser]. |
Model, Collection
|
Sent to the browser as their visible attributes. Read again from the database on every #[Server] call, never taken from the browser. |
Upload, #[Uploads] array
|
Files from a file input, stored by a #[Server] method. See Uploads. |
Model properties
Nitro reads a model property again from the database on each server call:
public ?Post $post = null;
public function mount(Post $post): void
{
$this->post = $post;
}
- A collection keeps its order, keeps each model as its own class, and leaves out rows that have been deleted since.
- If a single model's row has been deleted, a nullable property (
public ?Post $post) becomesnull, so the component can tell and the view can show it. A property that isn't nullable answers with a 404.
Arrays are values
As in PHP, $copy = $this->items makes a copy, so changing $copy never changes the property. The browser follows this rule too.
To read and change arrays and models in your views and methods, and to choose between them, see Arrays and models.
Property attributes
You can mark a property with these attributes to change how it behaves:
| Attribute | What it does |
|---|---|
#[Locked]
|
The browser can read it, but only the server can change it. |
#[Url]
|
Keeps the property in the query string (with as, history, keep and except). Nitro reads it before mount() and keeps it in sync with the property. |
#[Remember]
|
Keeps the value in the browser’s history entry, so back, forward and a reload bring it back, such as a half-filled form. |
#[Modelable]
|
Lets a parent bind this property with nitro:model. Only one property can have it. |
#[Validate]
|
Rules the server checks whenever the browser changes the property. |
Locking a property
The browser can read a property marked #[Locked], but only the server can change it, in
mount() or in a #[Server] method. Because the state is signed, the server refuses
a changed value.
class Wallet extends Component
{
public int $amount = 0;
#[Locked]
public int $balance = 100;
#[Server]
public function withdraw(): void
{
$this->balance -= $this->amount;
}
}
Locked doesn't mean current
Signing stops the browser from changing $balance. It doesn't stop the same visitor from sending an older state from their own session. So read anything that must be current, such as a balance or a permission, from the database in the #[Server] method.
Keeping a property in the query string
#[Url] keeps a property in the query string, so a search or a page number survives a reload and can be shared:
#[Url]
public string $search = '';
#[Url(as: 'p', history: true)]
public int $page = 1;
Validating properties
The browser can set any property that isn't #[Locked] to any value of its type.
Anyone can send a request without using the page, so $quantity could arrive as
-500. Before a #[Server] method runs, the server checks the type of what changed,
and its #[Validate] rules. If a rule fails, the method doesn't run, and the messages appear in
$errors.
#[Validate('integer|min:1|max:10', as: 'quantity')]
public int $quantity = 1;
#[Server]
public function checkout(): void
{
// $quantity is between 1 and 10 here
}
Validate everything a server method relies on
Put a rule on every property that a #[Server] method uses, or validate in the method itself. Calling $this->validate() without rules checks every property's #[Validate] rules, which is useful before saving a form.
Never put secrets in public properties
Every public property is sent to the browser, including #[Locked] ones, and a model property carries its visible attributes. Keep secrets in protected or private properties, or load them in the method that needs them.