I Love Writing JavaScript, But Livewire Is A Great Way To Avoid Writing JavaScript For Stupid Reasons
Josh Pollock
Posted on January 1, 2021
I suppose it is tempting, if the only tool you have is a React, to treat everything as if it were JSX.
So, I really love writing JavaScript. React plus TypeScript is some of my favorite code to write. One of the things I've done a lot of is build drag and drop user interface builders, React is amazing for that.
A lot of the development I do right now for my job is internal applications to help development and support of Ninja Forms. I am mainly working with Laravel. The complexity in these projects is scaling databases and processing a ton of data. On the other hand, the UI requirements are very basic -- mainly HTML tables and some graphs.
Initially, I was using Laravel as a headless API and for scheduled task processing. The user interface was a decoupled NextJS front-end. Starting a project with Next and Tailwind is a great way for me to get started. Also, it was totally overkill, and I had to figure out CORS and authentication. Sure, that is worth it in some cases, but I was building a UI for like 3 people to use.
So this was a perfect opportunity to try out LiveWire. I was able to build dynamic components that switch between tables and graphs as well as handle pagination and live search. All with out page re-loads.
Web Applications Are Difficult
One of the problems I run into with my job, is I'm good at figuring things out. In general this is a good thing, but it also means I can get stuck in a rabbit hole making something stupid work.
Livewire got me out of the "everything has to be an app" mindset. AJAX for pagination? That improves UX when searching. A full page refresh between screens that are for totally different data? Chill, it's fine.
Like, do you ever think about the amount of work we do to show people a loading spinner while we reset the entire state of the web page, just to avoid reloading the web page? Chill.
With Livewire I can create an SPA-like interface. For something basic, that we may or may not ever expand on, not having a lot of code, I think is a benefit.
For this app, I created one Livewire component per page. This was a little awkward, as it meant in addition to the PHP class for the component, I had a blade file with the layout for the page, with a livewire component tag and I had a blade file for the component.
The Laravel router returns a view that calls a blade file like this:
@extends('layouts.app')
@section('content')
<div class="flex items-center">
<div class="md:w-10/12 sm:w-full md:mx-auto">
@if (session('status'))
<div class="text-sm border border-t-8 rounded text-green-700 border-green-600 bg-green-100 px-3 py-4 mb-4" role="alert">
{{ session('status') }}
</div>
@endif
<div class="flex flex-col break-words bg-white border border-2 rounded shadow-md">
<div class="font-semibold bg-gray-200 text-gray-700 py-3 px-6 mb-0">
<h1>Page Title</h1>
<a
class="md:float-right"
href="/">Back
</a>
</div>
<div class="w-full p-6">
<p class="text-gray-700">
<livewire:component-name :id="$id">
</p>
</div>
</div>
</div>
</div>
@endsection
This is too many files for me, but it does mean that only part of the page is a Livewire component. What's neat about it is that the rest of the page is just a normal blade file. There could be other Livewire components. Or other JavaScript can be used for other parts of the page.
This blade file has to "wire" bindings, that's where the magic happens. The first on the input is wire:model="url"
. This binds the value of the property of the component PHP class to this input. I just needed to add a public property called URL to make it work:
<?php
namespace App\Http\Livewire;
use App\Models\Team;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class NewSite extends Component
{
public string $url;
public function mount(){
$this->url = '';
}
public function render()
{
return view('livewire.new-site');
}
public function create()
{
//Don't worry about this yet.
}
}
I used a typed property to force the value to string. That can cause issues with using the value server-side, for example, in the create callback of this method. If the input is never changed, the value of $this->url
will be null, which is not a string, and therefore there is an error.
My solution is to set it the property to an empty string using the mount()
method of the class. That method, gets called before render, which renders the blade template on the server and sets up whatever magic keeps the client in sync with the server.
If the variable does not need to be dynamically addressed in the client, you can also pass it to the view directly:
public function render()
{
return view('livewire.new-site', ['site' => 'https://hiroy.club']);
}
I really like that there isn't a special view function for Livewire. It's the same old view()
. There is some special syntax in the Livewire blade templates, but for the most part, it is just blade.
Another use for the mount()
method I mentioned before, is to get URL parameters. For example, if this component was for the route /sites/{id}
I could get the site ID from the mount()
function:
{
public $siteId;
public function mount($id){
$this->siteId = $id;
}
}
Anyway, back to the component for creating a site. We were discussing bindings and magic. The second binding is wire:click="create"
. This binds the click event of the button to the PHP class method create
.
That method has the value of the input, set in the property $url
, thanks to the first binding. I was able to use that to create the site, and associate it with the current user's team, like this:
public function create()
{
/** @var Team $team */
$team = Auth::user()->getCurrentTeam();
$site = $team->sites()->create([
'url' => $this->url
]);
return $this->redirect(sprintf('/sites/%s', $site->id));
}
Full Page Livewire
As I mentioned earlier, in the first project I used Livewire on, I did not use full page components. This approach allows for composing the page out of HTML, one or more Livewire components, and could include other frameworks.
That project started as a Laravel 7 app, and was upgraded to version 8, and I never got full page components to work. When I started a second project, using Jetstream they worked as expected.
That way there is only one blade file per page. Binding the component to the route, in web.php, is like binding an invokable controller:
Router::get( '/sites/{id}', \App\Http\Livewire\Site::class);
This is an example of a full page component blade file, which would work with the same PHP class I showed in the previous section:
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('New Site') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<label for="url">
URL
</label>
<input type="url" id="url" required wire:model="url" />
<button wire:click="create">Save</button>
</div>
</div>
</div>
The first part is the equivalent of a VueJS slot. I like how Jetstream breaks the page up into sections like this. I will likely add a sidebar and footer slot next.
LiveWire FTW!
Not every website needs to be a complete single page web application. Working with Livewire has reminded me that a traditional page navigation isn't such a bad thing. That page refresh prepares the user for a change, and it wipes out the unrelated state.
Livewire, especially with Jetstream is a great tool for building Laravel UI quickly. That makes it a great fast prototyping tool that should force you to ask the question -- do I need to replace this with a full SPA? Probably not, Livewire is very good.
This post is a brief intro to Livewire and why I think it is good. That said, I think I covered most of the important things you need to know. The docuemntation is quite good. If you sponsor the creator of the framework, Caleb Porzio on Github, you will get access to screencasts about using Livewire.
Posted on January 1, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.