Bridging PHP and JavaScript with Livewire

The background is set in a dark, but star-studded outerspace. Two planets are bridged together with a winding bridge, similar to two slightly-curled ribbons enclosing a highway road. The first planet to the left is aquatic with a pink hue, while the planet to the right is a terrain containing "JS" and "PHP" engraved in its soil.
Image by Annie Ruygt

This article talks about communication between PHP and JavaScript using Livewire. Livewire’s faster close to your users. Deploy your Laravel app globally with Fly.io, your app will be flying in no time!

What makes Livewire a gem is how it easily allows us to communicate data, methods, and events across PHP and JavaScript.

No long code, no extra syntax—just straightforward, easy PHP-JavaScript communication.

Version Notice. This article focuses on Livewire v2, details for Livewire v3 would be different though!

The Livewire Bridge

Livewire bridges the gap between PHP and JavaScript with concise communication syntax.

No longer would we need to write up our client’s http requests to call methods or get response data, nor would we need to dig up client arguments from the request received by our server.

Today, we’ll skim just a teensy bit beneath the surface of Livewire, and understand the role of its magical metadata.

From there we’ll see how tossing metadata between server and client creates the bridge for PHP and JavaScript communication.

Along the way we’ll create a simple chat-bot application:

A page containing a text box and a button with the ability to send messages to a chat bot. At the center of the page is a radiant, blue to pink colored heading "Talk to Me". Below it is a text box next to a button labeled "Send". A pointer moves from the bottom right towards the text box, and types "What's up?" inside the box. Then, the pointer clicks the button next to the text box. The browser responds to this action by displaying the text message a level below the text box, and makes a call to the server. Another level below the new displayed message, three bouncing, adjacent circular loaders is shown while the client waits for a response from the server. The loader eventually disappears and is replaced with a message from the server: "Up? I'm up in the clouds.".
The user can send any message at the click of a button, and the chat bot will respond with its answer.

It’s a bit rudimentary, but it will get us through some useful PHP-JS syntax:

  1. Triggering a PHP method from JavaScript
  2. Dispatching a JavaScript event from PHP
  3. Listening and Reacting to a PHP event from JavaScript
  4. Reading a PHP variable from JavaScript

You can check out our full repository here. You can also view the page here.

Under the Hood

A Livewire Component comes in two parts.

A Livewire Class processing requests in the server:

# /app/http/livewire/TalkToMe.php
<?php

namespace App\Http\Livewire;

use Livewire\Component;

class TalkToMe extends Component
{
    // Public attribute shared between server and client
    public $msg;

    // Called from the ui whenever a new msg is sent by the user
    public function getResponse()
    {
        $response = (new \App\Services\ChatBot)->processMessage($this->msg);
        $this->dispatchBrowserEvent(
          'response-received', 
          ['response'=>$response]
        );
    }

    // Render the view
    public function render()
    {
        return view('livewire.talk-to-me');
    }
}

And a Livewire view that is rendered in the client’s browser:

  • `wire:model` maps the input tag with a public attribute `$msg` configured in our Livewire Class above.
  • wire:model`.defer` disables calls to the server during changes on the input element.
# /resources/views/livewire/talk-to-me.blade.php
<div>
    <div>
        <input id="msg" wire:model.defer="msg" class="..." placeholder="What's on your mind?">
        <button onClick="sendMsg()">Send</button>
    </div>
</div>

We can embed this Livewire component in any blade component:

<body>
  <livewire:talk-to-me />
  @livewireScripts
</body>

In the process of rendering this blade component, the server intelligently ( courtesy of Livewire ) renders the <livewire:talk-to-me /> tag into a plain HTML version of the tag’s initialized view.

The content changes, but an initialized Livewire view generally looks like:

An image containing a div tag of an initialized Livewire view. The tag contains "wire:id" and "wire:initial-data" metadata, and encloses a div tag. The second div tag encloses an input tag with several attributes: an "id" of msg, a "wire:model.defer" of msg, a redacted class, and a placeholder of "What's on your mind?". Below the input tag is a button tag with a redacted class and an "onClick" listener that can trigger a sendMsg function. This button encloses the label "Send".
Livewire adds two additional attributes in our view’s root tag: wire:id and wire:initial-data

It comes enclosing the actual content of the view, with two additional attributes embedded into its root tag. Livewire embeds an id in wire:id, along with its metadata in wire:initial-data.

It is this metadata shared between server and client that wires the bridge of communication across PHP and JavaScript.

The Wire that is Metadata

The Livewire class in the server shares its state with the client’s Livewire view by embedding metadata within the wire:initial-data attribute.

A screenshot of the page containing the chat input box and button mentioned in the 1_demo.gif image is shown to the left of the rendered html for the initialized livewire view described in 2_init_wire.webp
The Livewire class’ details are jsonified into metadata, and embedded into wire:initial-data

This metadata contains details regarding the identity of the Livewire class ( fingerprint ), the data the component needs to keep track of ( serverMemo ), and instructions on what needs to be handled on page load in the client ( effects ).

Attribute Description
fingerprint Identifies the Livewire class( id, name, etc… )
serverMemo Data shared and persisted between server and client( i.e public attributes )
effects Details on effects the client needs to handle on page load

On the client side, the Livewire view has its own JavaScript, courtesy of the @livewireScripts directive, that grabs our initialized Livewire view. The view’s root element is passed to Livewire’s JavaScript class “Component” which parses and stores the metadata embedded in its wire:initial-data.

Afterwards, it sets up the necessary JavaScript listeners and handlers matching the requirements of each wire attribute found in the Livewire view.

Once the required JavaScript has been set up for our wire attributes, our component is ready for user interaction.

Building the Chatbot

The Component class above handles interaction in our Livewire view. It records interactions in its updateQueue list, and, at the right time, coordinates these interactions to the server by sending this list as the updates metadata to the server.

Let’s create a simple chat bot page where users can send messages and receive response. It’s a bit rudimentary, but perfect for showcasing the role that the updates metadata plays in Livewire’s PHP-JS communication.

The same gif image described in 1_demo.gif is shown left to an open browser's inspection table. A pointer moves towards the text box in the middle of the page, types a message, and clicks on a button next to it labeled "Send". The button click triggers a network request to be shown in the inspection table named "talk to me". A loader is shown and is eventually replaced with the message from the server.

<input id="msg" wire:model.defer="msg" class="..." placeholder="...">

Our message box above, <input wire:model.defer="msg"…, is wired to the $msg attribute in our Livewire PHP class.

Wiring our input element with wire:model creates a listener that reacts to changes on its value. Every change is pushed by the listener as a syncInput update to the Component’s updateQueue list and is immediately sent to the server as part of the updates metadata.

However, we don’t want every change in our input field to send a request to our server! This is why we’ve included the .defer chain to our wiring.

The .defer chain instructs its listener not to fire the syncInput instruction immediately, but instead to store up the instruction in the deferredActions list for future processing.

Now that changes on our input field is deferred with wire:model.defer, let’s manually apply a listener on the user’s button click to send messages to our chat bot.

<button class="..." onClick="sendMsg()">Send</button>

This onClick listener will call our JS function sendMsg() to process our user’s message.

Calling PHP methods from JavaScript

Above, we manually registered a button click to process our user’s chat message.

Now, let’s trigger the PHP getResponse() method from our JS function sendMsg():

<script>
function sendMsg(){
  ...
+  // Get response from server regarding our latest message. 
+  @this.getResponse(); 
}

You might be wondering ( hmmm ), why not just bind the button with the wire:click directive to easily map with a method call in our Livewire class?

Sometimes, it pays to mix and match JS with Livewire. Doing so gives us flexibility in separating client concerns from server concerns.

We can create some JS magic first before we actually call our Livewire method:

+ let chat = [];
function sendMsg(){
+    // Add user's msg to our chat transcript 
+    // This chat transcript is only available in the client
+    chat.push( {
+      msg:sanitizeHTML(
+           document.getElementById('msg').value
+       ),
+      sender:0
+    });

+    // Display Messages in our chat list ui
+    refreshTranscript();

    // Ask our server to respond to user's latest message
    @this.getResponse(); 
}

In our case, we’d want to save the message sent by the user in a list and display the new message in our UI. Only then would we want to request for a response from the server.

updateQueList is mapped to the updates metadata sent as a request payload to the server.

Triggering @this.getResponse() adds a callMethod update to our updateQueue list, and fires an immediate request to the server.

Fly.io ❤️ Laravel

Fly your servers close to your users—and marvel at the speed of close proximity. Deploy globally on Fly in minutes!

Deploy your Laravel app!

Tossing back the Metadata

Above, the client received the server’s Livewire class metadata, displayed its Livewire HTML, and now with a user’s click of a button, the client needs to coordinate some processing to the server through @this.getResponse().

How exactly does the Livewire view coordinate changes to the server?

It tosses the metadata back to the server.

An image showing the request payload sent by the client during button click. The request payload contains the following metadata: fingerprint, serverMemo, and updates. fingerprint and serverMemo is the same metadata previously sent by the server to the client, while the updates metadata contains instructions like syncInput for the server to process.
The previous metadata fingerprint and serverMemo along with an updates attribute is sent to the server

FIFO updates by the Server

What happens to subsequent requests our Livewire view makes to our Livewire class?

Thanks to the fingerprint.name sent back in wire:initial-data, our view sends a request to /…/livewire/message/{fingerprint.name}.

The server maps this to the proper Livewire class component, receives the metadata above from our client’s request payload, and “hydrates” the properties of the selected class component using serverMemo.

Afterwards, it performs any data-binding-updates ( syncInput ), action-calls ( callMethod ), and event-emissions ( fireEvent ) based on the instructions indicated in the updates metadata.

In our case, we had two instructions pushed in our client’s updates metadata:

An image zooming in at the updates metadata sent by the client. It is an array containing two instructions. The first instruction is a `syncInput` with payload of id, name, and value. The second instruction is a `callMethod` instruction with a payload of id, method, and params.
The updates metadata sent by the Livewire class contains instructions the Livewire class needs to process.

The first instruction with type syncInput is the deferred action from changing the value of <input id="msg" wire:model.defer="msg". This syncInput type instruction tells the Livewire class to update its attribute $msg with the value hello.

The second instruction is a callMethod type which triggers a getResponse method in our Livewire class. Which is basically this:

public function getResponse()
{
    $response = (new \App\Services\ChatBot)->processMessage($this->msg);
    $this->dispatchBrowserEvent(
      'response-received', ['response'=>$response]
    );
}

  • Our updated `$msg` attribute is sent to a custom class `\App\Services\ChatBot` for processing.
  • Once we have the response, we simply call the Livewire `dispatchBrowserEvent()` method to add the event `“response-received”` in our effects list.

There are only two instructions in our updates list. After the last instruction is processed by the server, it proceeds with steps to dehyrdate its details into metadata, and finally sends back an updated metadata to the client:

A screenshot of the response from the server. It contains metadata with attributes "effects" and "serverMemo".
The serverMemo contains updated data attributes, the effects contain change instructions needed in the Livewire view

The metadata sent back by the server contains only two attributes: effects and serverMemo. effects contain updates for the client to process, and serverMemo contains the updated data of the component.

Now that we’ve received the server’s response, let’s add the response to our chat transcript and display it in our UI.

PHP invokes JavaScript events

With the use of our Livewire metadata, we can actually invoke JS events from PHP. In our getResponse() method above, we called

$this->dispatchBrowserEvent('received-response',...)

Doing so pushes an event response-received to the dispatches array found inside the effects metdata sent back by the server:

A screenshot of the server's response zooming in on the dispatches list contained in its effects metadata. The dispatches metadata is an array containing an object. The object contains two attributes. The first attribute named "event" has a string value of "response-received". The second attribute named "data" contains a key-value pair of "response" as the key and "Wazzup" as the string value.

Livewire’s JavaScript dispatches an event for each item included in the effects.dispatches array. That means we’ll have a received-response event dispatched in our client’s browser.

Right there, in one line of code, is JavaScript event creation from PHP 😎

Reactive JavaScript

Now that we have the event created in our client, this means we can easily have our JavaScript listen to the event dispatched from our server’s effects metadata:

 window.addEventListener('response-received', event => {
    // Add the server's message to our chat transcript
    chat.push({
      msg: event.detail.response,
      sender: 1
    });

    // Refresh the chat transcript displayed 
    refreshTranscript();    
});

JavaScript reads PHP vars

Let’s say we’d have some initialized PHP attribute from our Livewire class:

public $goodMsgs;

public function mount()
{
  $this->goodMsgs = (new \App\Services\ChatBot)->goodMessageList();
}

From our JavaScript we can simply use the @js directive to read this PHP variable:

<script>
- let hints = JSON.parse('<?php echo json_encode($goodMsgs) ?>') 
+ let hints = @js($goodMsgs);

The @js directive is one of the Livewire blade directives that Livewire class from the server scans the Livewire view for.

The server reads this and renders a proper JavaScript variable for us. This is pretty neat, because using it removes our need to manually add JSON parsing in JavaScript for reading objects or arrays.

Brighter Magic with Clarity!

The more we look under the magic of Livewire, the more astonishing its workings become.

Because now, not only do we know the surface level of instantaneous, seamless interaction of wire elements with user interaction.

Under the hood, we now know, that behind every PHP Livewire component, is a JavaScript component coordinating interactions behind the scene.

PHP and JavaScript communication is bridged, all under the magic of the wire that is metadata. 🪄