DEV Community

Josh Pollock
Josh Pollock

Posted on

I Love Writing JavaScript, But Livewire Is A Great Way To Avoid Writing JavaScript For Stupid Reasons

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

Enter fullscreen mode Exit fullscreen mode

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.
    }
}
Enter fullscreen mode Exit fullscreen mode

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']);
    }
Enter fullscreen mode Exit fullscreen mode

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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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));
    }
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (8)

Collapse
 
realtebo profile image
Mirko Tebaldi

Only a side note. Every client action will result in a server ajax call. Yes, fully automated. But I don't want every click on a plus or a minus or on an accordion will make an ajax call. This cannot scale

Collapse
 
shelob9 profile image
Josh Pollock

You're correct about clicks. I don't think that part of this scales. That isn't a reason to say the whole idea doesn't scale.

If it was something with a lot of clicks, I'd write some client-side JavaScript to handle it. What I actually used this for was pagination, search, and other things that the end user perceives as page changes, this makes it faster for them.

Collapse
 
darakanoit profile image
Dahaka K

I plan to use it in conjunction with a roadrunner ( roadrunner.dev/ ) to avoid performance overhead and scale.

Collapse
 
jleonardolemos profile image
Leonardo Lemos

You do have the possibility to change some data binding parameters in order to prevent too much requests, you can reproduce any SPA with livewire with the same amount of ajax calls

Collapse
 
alexmartinfr profile image
Alex Martin • Edited

True! That's why Caleb recommends using Alpine* or even vanilla JavaScript in addition to Livewire. It's perfect for this kind of interaction šŸ˜‰

Collapse
 
jleonardolemos profile image
Leonardo Lemos

Nice article!!! LiveWire makes development experience more alike desktop/mobile apps where the state is preserved between the actions, sure this is a trick with ajax calls, but works very well and the developers are happy!! hahah

Collapse
 
dominic_rose_0eafadd77359 profile image
Dominic Rose

I find it easy (although not learned overnight) to write a bug-free front-end in react with typescript and vs code. The code is in a functional style with pure functions and JSX/TSX so it's clean an reliable. The IDE support and typing system is just perfect. And it scales well (number of developers, size of the codebase).

That feeling of simplicity and perfection goes away with a back-end. It's still difficult today to just set up a full stack app in typescript in one project, one directory that does server-side rendering and front-side rehydration. I just don't want to use a framework like Next.js. It's not necessary in JS, why would it be necessary in TS?

Collapse
 
realtebo profile image
Mirko Tebaldi

Provably you are the last dev loving js