Component Operations

Target specific components by name and update their state or invoke their methods directly from the server.

Updating Component State

Use componentState() to update a specific component's Alpine state from the backend. The component must be registered with the x-component directive.

// Controller
public function addToCart(Request $request)
{
    $product = Product::find($request->input('product_id'));

    // Add to user's cart...
    $cart = $request->user()->cart();
    $cart->add($product);

    // Update the cart component directly
    return gale()
        ->componentState('cart', [
            'items' => $cart->items()->toArray(),
            'total' => $cart->total(),
            'count' => $cart->count(),
        ]);
}

The component on the frontend:

<!-- Cart component in header -->
<div x-data="{ items: [], total: 0, count: 0 }"
     x-component="cart">

    <span class="cart-badge">
        <span x-text="count"></span>
    </span>

    <div class="cart-dropdown">
        <template x-for="item in items">
            <div x-text="item.name"></div>
        </template>
        <div class="total">Total: $<span x-text="total"></span></div>
    </div>
</div>

RFC 7386 Merge Semantics

State updates use RFC 7386 JSON Merge Patch semantics. This means updates merge recursively with existing state, arrays are replaced entirely, and null values delete properties.

// Current component state:
// { user: { name: 'John', email: 'john@example.com' }, items: [1, 2] }

return gale()->componentState('profile', [
    'user' => [
        'name' => 'Jane',  // Updates name, keeps email
    ],
    'items' => [3, 4, 5],  // Replaces array entirely
]);

// Result: { user: { name: 'Jane', email: 'john@example.com' }, items: [3, 4, 5] }

Only If Missing

Use the onlyIfMissing option to initialize state without overwriting existing values. This is useful for setting defaults when a component may already have state from a previous interaction.

// Only set defaults if they don't already exist
return gale()->componentState('settings', [
    'theme' => 'light',
    'notifications' => true,
], [
    'onlyIfMissing' => true,
]);

Invoking Component Methods

Use componentMethod() to call methods defined on a component's x-data object. This allows the backend to trigger client-side behavior like recalculating totals, refreshing data, or triggering animations.

// Controller - Invoke a method on a component
public function applyDiscount(Request $request)
{
    $coupon = Coupon::where('code', $request->code)->first();

    if (!$coupon) {
        return gale()->messages(['coupon' => 'Invalid coupon code']);
    }

    // Tell the cart component to recalculate with the discount
    return gale()->componentMethod('cart', 'applyDiscount', [
        $coupon->percentage,
        $coupon->code,
    ]);
}

The component with the target method:

<div x-component="cart"
     x-data="{
         items: [],
         total: 0,
         discount: 0,
         couponCode: null,

         applyDiscount(percentage, code) {
             this.discount = percentage;
             this.couponCode = code;
             this.recalculate();
         },

         recalculate() {
             const subtotal = this.items.reduce((sum, item) => sum + item.price, 0);
             this.total = subtotal * (1 - this.discount / 100);
         }
     }">

    <!-- Cart UI -->
    <div x-show="couponCode" class="discount-badge">
        <span x-text="discount + '% off with ' + couponCode"></span>
    </div>

</div>

Passing Arguments

Arguments are passed as an array and spread when the method is invoked. You can pass any JSON-serializable values: strings, numbers, booleans, arrays, and objects.

// No arguments
gale()->componentMethod('dashboard', 'refresh');

// Single argument
gale()->componentMethod('player', 'seek', [120]);  // 2 minutes

// Multiple arguments
gale()->componentMethod('chart', 'updateData', [
    [10, 20, 30, 40],  // data array
    'line',           // chart type
    ['animate' => true] // options object
]);

// Complex object argument
gale()->componentMethod('notification', 'show', [[
    'title' => 'Success!',
    'message' => 'Your order has been placed.',
    'type' => 'success',
    'duration' => 5000,
]]);

Combining Operations

You can combine component operations with other Gale methods in a single response. This allows you to update multiple components, show messages, and manipulate the DOM all at once.

public function checkout(Request $request)
{
    $order = $this->processOrder($request);

    return gale()
        // Update the cart component to empty state
        ->componentState('cart', [
            'items' => [],
            'total' => 0,
            'count' => 0,
        ])
        // Update the order tracker with the new order
        ->componentState('order-tracker', [
            'orderId' => $order->id,
            'status' => 'processing',
        ])
        // Trigger the confetti animation
        ->componentMethod('celebration', 'showConfetti')
        // Show a success message
        ->messages(['_success' => 'Order placed successfully!']);
}

Practical Patterns

Live Notifications

Push notifications to a specific component from anywhere in your backend:

// Notification component
<div x-component="notifications"
     x-data="{
         items: [],

         add(notification) {
             this.items.push({
                 ...notification,
                 id: Date.now()
             });

             // Auto-dismiss after duration
             if (notification.duration) {
                 setTimeout(() => this.dismiss(notification.id), notification.duration);
             }
         },

         dismiss(id) {
             this.items = this.items.filter(n => n.id !== id);
         }
     }">

    <template x-for="item in items" :key="item.id">
        <div :class="'notification notification-' + item.type">
            <span x-text="item.message"></span>
            <button @click="dismiss(item.id)">×</button>
        </div>
    </template>
</div>
// Push notification from backend
gale()->componentMethod('notifications', 'add', [[
    'type' => 'success',
    'message' => 'Payment received!',
    'duration' => 5000,
]]);

Modal Control

Control modals from the server:

<!-- Modal component -->
<div x-component="modal"
     x-data="{ open: false, title: '', content: '' }">

    <div x-show="open" class="modal-overlay">
        <div class="modal-content">
            <h2 x-text="title"></h2>
            <div x-html="content"></div>
            <button @click="open = false">Close</button>
        </div>
    </div>
</div>
// Open modal with content from backend
public function showTerms()
{
    return gale()->componentState('modal', [
        'open' => true,
        'title' => 'Terms of Service',
        'content' => view('partials.terms')->render(),
    ]);
}

Update search results while preserving UI state:

public function search(Request $request)
{
    $results = Product::search($request->input('q'))
        ->take(20)
        ->get();

    // Update results while keeping the search input focused
    return gale()->componentState('search', [
        'results' => $results->toArray(),
        'loading' => false,
        'totalCount' => $results->count(),
    ]);
}

Error Handling

When a component or method is not found, Gale logs a warning but doesn't throw an error. This allows your application to gracefully handle cases where components haven't registered yet or have been removed from the DOM.

// Console warnings (not errors):
// [Gale] Component "cart" not found for method invocation
// [Gale] Method "refresh" not found on component "cart"

// Check existence before calling if needed:
if ($components.has('cart')) {
    $components.invoke('cart', 'refresh');
}

SSE Protocol

Component operations are sent via Server-Sent Events. Understanding the protocol can help with debugging.

// componentState() sends:
event: gale-patch-component
data: component cart
data: state {"items":[],"total":0}

// componentMethod() sends:
event: gale-invoke-method
data: component cart
data: method applyDiscount
data: args [20,"SAVE20"]

Component State vs Global State

Method Use Case Scope
gale()->state() Shared data across all components Global x-data
gale()->componentState() Update a specific named component Single component
gale()->componentMethod() Trigger behavior on a component Single component

Use global state when data should be accessible everywhere (user info, theme, etc.). Use component operations when targeting specific UI elements that manage their own state.

On this page