In Livewire, how do the #[Reactive] and #[Modelable] attributes differ when a parent and a nested child component share a value?
answer
- props are copies by default
- #[Reactive]: parent to child only
- child mutating it throws
- #[Modelable]: wire:model on the child tag
- one modelable property per component
basics
~20 s#[Reactive] makes a Livewire child property follow the value the parent passes on each parent update, one way; the child may not change it. #[Modelable] lets the parent put wire:model on the child tag, syncing both ways.
solid answer
~40 sBy default a Livewire prop is copied at mount and never updated. `#[Reactive]` on the child's property makes it **one-way reactive**: whenever the parent re-renders, it sends the new value to the child, which re-renders with it. The child must not change a reactive prop; doing so throws `CannotMutateReactivePropException`. It costs extra payload on every parent update, so use it sparingly. `#[Modelable]` marks the **single** child property that a parent can bind with `wire:model`, as in `<livewire:qty-picker wire:model="qty" />`: when the child's value changes, the parent's `qty` updates, which makes reusable input-like components. Its root element cannot carry `wire:model` itself. Reactive is for display children; modelable is for input children.
code
php · 20 lines<?php
use Livewire\Attributes\Modelable;
use Livewire\Component;
new class extends Component {
#[Modelable]
public int $value = 1;
public function increment(): void
{
$this->value++;
}
};
?>
<div>
<input type="number" wire:model="value" min="1">
<button wire:click="increment">+</button>
</div>go deeper
Recall that props are copies, #[Reactive] makes them follow the parent, and #[Modelable] enables wire:model on a child tag.
Explain the one-way versus two-way flow, the mutation exception, the single modelable property and the root-element rule.
Weigh reactive props' payload and re-render cost against events or merging components, and design reusable modelable inputs.
Set rules for component boundaries so shared state has one owner and communication patterns stay consistent across teams.
## The default: props are copies When a parent renders `<livewire:cart-summary :items="$items" />`, the child receives `items` once, at mount. Later parent updates send **only the parent's state** to the server, so the child keeps its own copy. That keeps requests small, but it surprises developers used to reactive props in front-end frameworks. Livewire offers two attributes to share values deliberately. ## #[Reactive]: parent to child, one way ```php use Livewire\Attributes\Reactive; // Child: cart summary #[Reactive] public array $items; ``` 1. The parent changes `$items` in an action. 2. During that parent request, Livewire passes the new value to the child as a prop. 3. The child re-renders with the new value; its update hooks for that property run. Rules and costs: - The child **must not modify** a reactive property. If it does, Livewire throws `CannotMutateReactivePropException` when the child is dehydrated. Changes flow down only. - Reactive props add data to every parent update and cause child re-renders, so use them where a child genuinely mirrors parent state, such as a totals panel beside an editable cart. ## #[Modelable]: two-way binding with wire:model ```php use Livewire\Attributes\Modelable; // Child: quantity picker #[Modelable] public int $value = 1; ``` ```html <!-- Parent view --> <livewire:qty-picker wire:model="qty" /> ``` - The parent binds with `wire:model`, exactly as on an `<input>`. When the child's `$value` changes, the parent's `$qty` updates, and the parent's value initialises the child. - Parent-side modifiers such as `.live` or `.live.debounce.500ms` control when the parent syncs. - Only **one** modelable property per component is supported; only the first is bound. - The child's **root element cannot carry its own `wire:model`**, because Livewire injects the parent binding there; wrap the inner input in a `<div>`. Livewire throws `ModelableRootHasWireModelException` otherwise. ## Side by side | | `#[Reactive]` | `#[Modelable]` | |---|---|---| | Direction | Parent to child | Both ways | | Set up in parent with | A normal prop, `:items="$items"` | `wire:model="qty"` on the child tag | | Child may change it | No, throws | Yes, that is the point | | Per component | Any number of properties | One property | | Typical child | Display: counters, summaries, charts | Input: pickers, editors, custom fields | ## How the values travel Both attributes ride on requests that already happen. For a reactive prop, the **parent's** request carries the new value to the child as part of rendering the parent, and the child is updated in the same response. For a modelable property, the child's value is **entangled** with the parent's bound property on the client, and the parent's binding modifiers decide when the parent's request is sent. Neither attribute makes the child's state visible to the parent's PHP code directly; the parent sees only its own bound property. ## Alternatives - **Events** (`dispatch()` and `#[On]`) for loosely coupled components, or when several components react. - **`$parent.method()`** when a child only needs to call its parent. - Keeping the markup in the same component, when the child exists only to split a template, avoids sharing state altogether. ## Cart example A cart page lists lines with a quantity picker per line and a header cart counter elsewhere. The quantity picker is a **modelable** child bound to each line's quantity. The order summary beside the lines is a **reactive** child that re-renders whenever the parent's lines change. The header counter is not a child at all, so it learns about changes through an **event**.
- What happens if a Livewire child assigns a new value to a #[Reactive] property in one of its actions?Livewire throws `CannotMutateReactivePropException` when the child is dehydrated. Reactive props are owned by the parent; to change the value, the child must ask the parent, for example through `$parent.method()` or an event, and let the new value flow back down.
- Why can't a Livewire #[Modelable] component's root element be the <input> with wire:model?Livewire attaches the parent's binding to the child's root element. A second `wire:model` on the same element conflicts, so Livewire throws `ModelableRootHasWireModelException`. Wrapping the input in a `<div>` gives the parent binding its own element.
saying these in an interview costs you the question
- Livewire props update automatically when the parent's value changes
- A child can freely modify a #[Reactive] property
- #[Modelable] can be put on several properties of one component
- #[Reactive] lets the child push its own changes back up to the parent
- #[Modelable] makes a prop one-way from parent to child