Sodeeq Elusoji Software developer | entrepreneur | table tennis player

Deep dive into Laravel Livewire

8 min read 2451

Deep Dive Laravel Livewire

Laravel Livewire is a full-stack Laravel framework for building dynamic interfaces. In the not-so-distant past, to build an app with Laravel, you either had to:

  1. Write Blade templates and render your app on the server side
  2. Write your backend as APIs that accept and respond with JSON, and then have a frontend framework like Vue, React, or Angular to consume the APIs and implement the UI

But now, we have a third option: Livewire. I’d have loved to go in depth on pros and cons of options 1 and 2 above, but that has already been done justice in this well-written post here.

In this article, we will be diving deep into Livewire and seeing how it can be applied in real-world applications.

What we will build

We will build a simple blogging app with the following features:

  1. Anyone can create an account and log in
  2. Logged-in users can create and update their own posts
  3. Anyone can read posts

Prerequisites and setup

This tutorial assumes you are fairly experienced with Laravel (note that this tutorial uses Laravel 7.x). No prior experience with Livewire is required — I think that’s why we’re here anyways.

Of course, to begin, we have to have our development environment set up. First, create a new Laravel app:

composer create-project --prefer-dist laravel/laravel:^7.0 blog-wire

Then install the Livewire package with composer:

composer require livewire/livewire

Create a new database and add your database credentials to the .env file. And that’s all we need to get started “Livewiring”!

How does Livewire work?

Before we begin, it’s good to have an idea how Livewire does its thing. To put it summarily:

  • Livewire renders the initial component output to the page — yes, like the typical server-rendered Blade template
  • When an interaction happens, Livewire makes an AJAX request to the server with updated data
  • The server re-renders the component and responds with updated HTML
  • Livewire then intelligently mutates the DOM according to what changed

As you can see, it’s like having your frontend and backend in one place, with no need to repeat logic.

We made a custom demo for .
No really. Click here to check it out.

Key concepts in Livewire

Activating Livewire on a page

To get Livewire working on a page, you need to include the Livewire styles and scripts on each page that need them. Usually, these would go into your base template. You’d do that using @livewireStyles and @livewireScripts:

//app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>@yield('title')</title>
    @livewireStyles
</head>
<body>
    @yield('content')
    @livewireScripts
</body>
</html>

Livewire components

Livewire does all of its goodness around its components. Livewire components are quite similar to typical Laravel Blade class-based components. Let’s take a quick look at the two.

Creating a Laravel Blade component

You’d create a Laravel Blade component by running the following command:

php artisan make:component Alert

This will create a new Alert.php class file and place it in the App\Views\Components folder. Then, a corresponding view template is created and placed in resources/views/components. To display the component, you can then use this Blade syntax: <x-alert/>.

You can further explore Laravel Blade components in the documentation.

Creating a Livewire component

To create a Livewire component, run the following command:

php artisan make:livewire Alert

The command will also create two new files: app\Http\Livewire\Alert.php and a view template resources/views/livewire/alert.php.

You can render a Livewire component using either <livewire:alert /> or @livewire('alert').

As you can see, the commands look quite similar. The only major difference is that with Livewire components, there’s a real-time sync (no page refresh required) between the component class and its view template. We will see how this works shortly.

Livewire properties

Public properties on your component classes are made available to the component template view. It doesn’t stop there — the value of the property is synchronized in real time to the view, such that when you update the value of the property in the view, it is automatically updated in the component class.

//App\Http\Livewire\Alert.php
<?php
class Alert extends Component{
  public $message = "Our alert message";
}

// livewire/alert.blade.php
<div>
  <input wire:model="message">
  <br/>
  {{ $message }}
</div>

To bind a component property to an html input element, you’d use the following sytax:

wire:model="property name"

By typing into the input box, you will see the value of $message updating in real time. This is very similar to the concept of data binding in frameworks like Vue.js, React, and Angular. Learn more about Livewire properties here.

Livewire actions

Just as you can bind data in the view template to public properties of components, you can also map client-side events to methods in your components. For example, you can respond to click events, keyup and keydown events, etc. using methods defined in your component class.

Let’s look at an example:

<?php

use Livewire\Component;
class PostAlert extends Component{
  public $liked = true;
  public function render(){
    return view('livewire.post-alert');
  }

  public function toggleLike(){
    $this->liked = !$this->liked;
  }
}


// livewire/post-alert.blade.php
<div>
    <h4>Seeing livewire action in action 😜</h4>
    <button class="btn btn-primary" wire:click="toggleLike()">
        Like
    </button>
    @if ($liked)
        <i class="fa fa-heart text-danger h4"></i>
    @else
        <i class="fa fa-heart text-secondary h4"></i>
    @endif
</div>

In the component class above, we created a method toggleLike() that toggles the value of the liked property to its opposite Boolean value. In the template view, we have a button and a heart icon that is either colored red or gray based on the value of the liked property.

We used the wire:click=[action name] syntax to bind the toggleLike method to the click event.

Livewire Action ToggleLike Event
Livewire action toggling a button.

A lot Livewire’s use cases revolve around properties and actions, and as such, they are very important to understand. These concepts can be applied to things like create form, edit form, delete form, etc. Read more about Livewire actions here.

Data validation

Livewire makes data validation seamless. To validate data coming from a form template view, you’d write a $rules property that contains your validation rules, just as you would in Laravel. Thereafter, you call the $this→validate() in the method doing the validation.

Let’s look at a form for creating a blog post:

...
class CreatePost extends Component
{
    public $title, $body;
    public $success;
    protected $rules = [
        'title' => 'required|string|max:220',
        'body' => 'required'
    ];

    public function render()
    {
        return view('livewire.create-post')
            ->extends('layouts.app')
            ->section('content');
    }
    public function create(){
        $this->validate();
        Post::create([
            'title' => $this->title,
            'slug' => Str::slug($this->title),
            'body' => $this->body,
            'author_id' => auth()->id()
        ]);

        $this->success = true;
    }
}


// livewire/create-post
<div class="container">
    @if ($success)
        <div class="alert alert-success">
            Post has been created successfully
        </div>
    @endif
    <form wire:submit.prevent="create">
        <div class="form-group">
            <label for="Post title">Post title</label>
            <input wire:model="title" type="text" name="title" id="title" class="form-control" placeholder="Title of the post">
            @error('title') <span class="error">{{ $message }}</span> @enderror
        </div>
        <div class="form-group">
            <label for="Post body">Post Body</label>
            <textarea name="body" id="body" placeholder="Body of post here..." wire:model="body" class="form-control"></textarea>
            @error('body') <span class="error">{{ $message }}</span> @enderror
        </div>
        <div>
            <button class="btn btn-primary" type="submit">Publish</button>
        </div>
    </form>
</div>

In the form code above, when the user submits the post, and it doesn’t pass the validation, the validation errors are displayed, all without a page refresh.

Getting started with Laravel Livewire

Enough said — let’s get to action. You can follow along in the GitHub repo as we build our demo app.

Since we want signed-in users to be able to manage their own posts, they have to create an account first. We’ll use Laravel’s built-in authentication system for this.

First, install the laravel/ui composer package:

composer require laravel/ui

Then run php artisan ui vue --auth to scaffold the entire authentication system, followed by php artisan migrate to do your DB migrations.

N.B., most of the things we used to do with controllers will now be done using Livewire components.

Let’s go on to create the model needed for our blog posts app, App\Post:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
    use SoftDeletes;

    protected $guarded = [];
    public function author(){
        return $this->belongsTo(User::class, 'author_id', 'id');
    }
}

Now we’ll create our migration file,
php artisan make:migration create_posts_table--table=posts:

Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('slug')->unique();
            $table->longText('body');
            $table->integer('author_id');
            $table->timestamps();
            $table->softDeletes();
});

Creating components

We will be creating a component for each blog action we want, i.e., create post, edit post, list posts, and view post. Let’s go ahead and create the components:

  • php artisan make:livewire CreatePost
  • php artisan make:livewire EditPost
  • php artisan make:livewire ListPost
  • php artisan make:livewire HomePost
  • php artisan make:livewire ReadPost

We can render a Livewire component directly from routes like this:

Route::get('/posts/create', [\App\Http\Livewire\CreatePost::class, '__invoke'])->middleware('auth');

Instead of calling controller actions, we will be routing to the Livewire components, as shown above. Let’s now add all the routes we’ll need in web.php:

Route::get('/', function () {
    return view('index');
});
Auth::routes();
Route::get('/post/{slug}', [\App\Http\Livewire\ReadPost::class, '__invoke']);
Route::get('/home', '[email protected]')->name('home');
Route::get('/posts/create', [\App\Http\Livewire\CreatePost::class, '__invoke'])->middleware('auth');
Route::get('/posts/{id}/edit', [\App\Http\Livewire\EditPost::class, '__invoke'])->middleware('auth');

The component to render the list of articles will look like this:

//ListPost.php
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class ListPost extends Component
{
    public function render()
    {
        $posts = \App\Post::latest()->paginate(20);
        return view('livewire.list-post', ['posts' => $posts])
            ->extends('layouts.app')
            ->section('content');
    }
}



//livewire/list-post.blade.php
<div>
    <h4>My Posts <a href="{{ url('posts/create') }}" class="btn btn-primary"><i class="fa fa-plus"></i> Add new</a></h4>
    <ul class="list-group list-group-flush">
        @forelse ($posts as $post)
            <li class="list-group-item">

                <div class="float-right">
                    <a href='{{ url("posts/{$post->id}/edit") }}' class="btn btn-primary"><i class="fa fa-edit"></i> Edit</a>
                </div>
                <div>
                    <h5>{{ $post->title }}</h5>
                    <p>{!! substr(strip_tags($post->body), 0, 200) !!}</p>
                    <small class="text-muted">Published {{ $post->created_at }}</small>
                </div>

            </li>    
        @empty
            <li>You have not written any posts yet, write one now</li>
        @endforelse

    </ul>
</div>

Then, to create a post, we will use this:

//CreatePost.php
<?php
namespace App\Http\Livewire;
use App\Post;
use Livewire\Component;
use Illuminate\Support\Str;
class CreatePost extends Component
{
    public $title, $body;
    public $success;
    protected $rules = [
        'title' => 'required|string|max:220',
        'body' => 'required'
    ];

    public function render()
    {
        return view('livewire.create-post')
            ->extends('layouts.app')
            ->section('content');
    }
    public function create(){
        $this->validate();
        Post::create([
            'title' => $this->title,
            'slug' => Str::slug($this->title),
            'body' => $this->body,
            'author_id' => auth()->id()
        ]);

        $this->success = true;
    }
}

In the above component, we create public variables to hold the title and body content of a blog post, as well as a success variable to indicate whether post creation is successful.

In the render() method, Livewire allows us to specify the layout file to use in rendering the component and the section where we want it displayed via the extends() and section() methods, respectively.

Now the template view looks like this:

<div class="container">
    @if ($success)
        <div>
            <div class="alert alert-success">
                Post has been created successfully. <a href="{{ url('/home') }}">View all posts</a>
            </div>

        </div>
    @endif

    <form wire:submit.prevent="create">
        <div class="form-group">
            <label for="Post title"&gt;Post title</label>
            <input wire:model="title" type="text" name="title" id="title" class="form-control" placeholder="Title of the post">
            @error('title') <span class="error">{{ $message }}</span> @enderror
        </div>
        <div class="form-group">
            <label for="Post body">Post Body</label>
            <textarea name="body" id="body" placeholder="Body of post here..." wire:model="body" class="form-control"></textarea>
            @error('body') <span class="error">{{ $message }}</span> @enderror
        </div>
        <div>
            <button class="btn btn-primary" type="submit">Publish</button>
        </div>
    </form>
</div>

If you navigate your browser to /posts/create, you should see the creation form displayed:

Creation Form Display

This is a fairly basic example of how Livewire can be applied in the real world.

Conclusion

Livewire bridges the gap between backend and frontend. You get the benefit of real-time interactivity without having to write a lot of JavaScript by yourself. If you’ve used Vue.js before, it is very easy to see Livewire’s benefits.

Livewire isn’t a great choice for apps that are heavily demanding on the client side. But in situations where you want a server-rendered app with a sprinkle of reactivity, Livewire will serve you well.

Again, the app we built in this tutorial can be found on GitHub.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Sodeeq Elusoji Software developer | entrepreneur | table tennis player

One Reply to “Deep dive into Laravel Livewire”

  1. Hi there.
    Github repository is empty. I want to learn what you are wroted there. Please, help.

Leave a Reply