Courses

[NEW] Livewire 3 From Scratch: Practical Course

Practical Start: Form "Live-Submit" Demo

I want to start this course by demonstrating Livewire to you. Its purpose is to create dynamic pages with no browser refresh but without writing JavaScript. So, let's start with a simple example of submitting the form and showing the success message on the same page, clearing up the form fields.

saved message

Let's start by installing Livewire into a Laravel project and creating the first Component.

By the of the lesson, you will know how to:

  • Install Livewire and create a Livewire component.
  • Use component properties and bind them into a form.

Before Starting

This course is about Livewire. In this course, for simple styling, I am using Tailwind CSS. You can find how to set up Tailwind CSS in the official documentation.

If you are using Breeze starter kit, it adds AlpineJS, which will conflict with Livewire v3 because Livewire v3 is bundled with AlpineJS. You need to remove AlpineJS from Breeze, then. How to do it you can read in the post Livewire 3 and Laravel Breeze Error: Alpine.js Conflict.

We have made a skeleton app based on Breeze, which you can use to quickly have Breeze-style pages without authentication.

Another option for quick scaffolding is to use Laravel Jetstream. Version 4 comes with Livewire 3 out-of-the-box.

If you will be using Full Page Components then you need to set different path for layout. This can be done globally in the config file.

config/livewire.php:

// ...
'layout' => 'layouts.app',
// ...

Install Livewire and Create First Component

In the Terminal, we run this:

composer require livewire/livewire:^3.0

Now that we have installed Livewire let's create a Component and call it CreatePost.

php artisan make:livewire CreatePost

This command creates two files:

  • a PHP class: app/Livewire/CreatePost.php
  • and a Blade View file: resources/views/livewire/create-post.blade.php

Now that we have a Livewire component, we need to render it inside the Laravel Blade file. You can do that using the <livewire:component-name /> syntax.

So, to render our CreatePost component, we need to add <livewire:create-post /> where we want to show the content of it.

For example, we have this Route in Laravel:

routes/web.php:

Route::view('posts/create', 'posts.create');

And then we have this Blade file:

resources/views/posts/create.blade.php:

<html>
<body>
// ... full layout header
 
<livewire:create-post />
 
// ... full layout footer
</body>
</html>

For a simple demonstration that it works, let's add a static text to the Livewire component in the resources/views/livewire/create-post.blade.php Blade file.

resources/views/livewire/create-post.blade.php:

<div>
Create Post Livewire Component
</div>

Important: The Livewire component needs to have a root <div> HTML tag.

Now, visit the page /posts/create where your Livewire component should be rendered, and you will see that static text from the Component.

livewire component rendered

Great, our component is loaded!


Properties and Data Binding

Now, let's add a simple form instead of that static text.

resources/views/livewire/create-post.blade.php:

<div>
<form method="POST">
<div>
<label for="title" class="block font-medium text-sm text-gray-700">Title</label>
<input id="title" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" type="text" />
@error('title')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
<div class="mt-4">
<label for="body" class="block font-medium text-sm text-gray-700">Body</label>
<textarea id="body" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"></textarea>
@error('body')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
<button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
Save
</button>
</form>
</div>

The form should look similar to this:

livewire form

So now that we have a form, each input field should have a public property inside the Livewire PHP class.

app/Livewire/CreatePost.php:

use Livewire\Component;
use Illuminate\Contracts\View\View;
 
class CreatePost extends Component
{
public string $title = '';
 
public string $body = '';
 
public function render(): View
{
return view('livewire.create-post');
}
}

Now, we need to bind those properties to the inputs in the Blade file using the wire:model directive.

resources/views/livewire/create-post.blade.php:

<div>
<form method="POST">
<div>
<label for="title" class="block font-medium text-sm text-gray-700">Title</label>
<input id="title" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" type="text" />
<input id="title" wire:model="title" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" type="text" />
@error('title')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
<div class="mt-4">
<label for="body" class="block font-medium text-sm text-gray-700">Body</label>
<textarea id="body" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"></textarea>
<textarea id="body" wire:model="body" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"></textarea>
@error('body')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
<button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
Save
</button>
</form>
</div>

That binding means that whenever the input value changes, the Livewire component variables will receive those new values, and then you may perform PHP operations with them.


Action Methods: Submit Form Data

Now, let's add an action to save a post to the DB. For that, we create a method inside the Livewire PHP class. This method can be called whatever you want. We will just name it save().

app/Livewire/CreatePost.php:

class CreatePost extends Component
{
// ...
 
public function save(): void
{
dd('save'); // Just for demonstration, for now
}
 
// ...
}

For now, we hardcode the text just to show that it works.

Now, we need to call this method from Blade upon pressing the Save button. This is done by using the wire:submit directive and specifying the method.

resources/views/livewire/create-post.blade.php:

<div>
<form method="POST">
<form method="POST" wire:submit="save">
 
// ...
 
</form>
</div>

Now, after clicking the Save button, we will have a dd() message save.

dd post save

This means the button works! Now, let's replace the dd() with saving a post to the DB.

app/Livewire/CreatePost.php:

use App\Models\Post;
 
class CreatePost extends Component
{
// ...
 
public function save(): void
{
dd('save');
Post::create([
'title' => $this->title,
'body' => $this->body,
]);
}
 
// ...
}

After clicking Save, we should see a new post if we check the posts table in the DB.

post in the db


Form Validation

Currently, our form doesn't have any validation after submitting, so let's add a few rules.

One of the ways to add them, released in the Livewire 3, is to use PHP 8 syntax of attributes on top of each property variable.

app/Livewire/CreatePost.php:

use Livewire\Attributes\Rule;
 
class CreatePost extends Component
{
#[Rule('required|min:5')]
public string $title = '';
 
#[Rule('required|min:5')]
public string $body = '';
 
public function save(): void
{
$this->validate();
 
Post::create([
'title' => $this->title,
'body' => $this->body,
]);
}
 
// ...
}

And that's it. That is how easy it is to add validation in the Livewire component. And all Laravel validation rules also work here.

After clicking the Save button, you will see validation error messages.

failed validation

Again, to reiterate: this happens without a page refresh but without writing any JavaScript. That's the beauty of Livewire.


Show Success Message and Clear Form

Now, after saving the post, let's show a success message above the form and clear the form.

First, the success message. For this, we need a new boolean property $success, with a false default value.

class CreatePost extends Component
{
#[Rule('required|min:5')]
public string $title = '';
 
#[Rule('required|min:5')]
public string $body = '';
 
public bool $success = false;
 
// ...
}

In the Blade file, we need to make a simple if-check to see whether $success is true, and then the message will be shown.

resources/views/livewire/create-post.blade.php:

<div>
@if($success)
<span class="block mb-4 text-green-500">Post saved successfully.</span>
@endif
 
// ...
</div>

Notice. I'm not sure if you fully understand by now, but I will reiterate: each property of the Livewire class is automatically available in the Blade file as a variable. There is no need to pass them manually.

All that is left is to set success to true and reset the form.

app/Livewire/CreatePost.php:

class CreatePost extends Component
{
// ...
 
public function save(): void
{
$this->validate();
 
Post::create([
'title' => $this->title,
'body' => $this->body,
]);
 
$this->success = true;
 
$this->reset('title', 'body');
}
 
// ...
}

The method $this->reset() sets all the passed properties to their default values, which are empty strings in our case.

After the post is saved, we see a success message.

saved message

Great, it all works!

So, does it give you an overview and "the first feeling" of Livewire? To reiterate, it gives you the opportunity to process form input variables without refreshing the page, but it all happens in a PHP/Laravel code familiar to us. No JavaScript to write. At all.

Curious to find out more? Let's dive deeper!

avatar

For some reason the reset did not work however the success message did appear and it was saved in the database. I did copy and past you code in.

By the way hear is the composer json and package json content


 "require": {
        "php": "^8.2",
        "guzzlehttp/guzzle": "^7.8",
        "laravel/breeze": "^1.23.1",
        "laravel/framework": "^10.21",
        "laravel/helpers": "^1.6",
        "laravel/sanctum": "^3.2.6",
        "laravel/tinker": "^2.8.2",
        "livewire/livewire": "^3.0.1",
        "tpetry/laravel-mysql-explain": "^1.0.2"
    },
    "require-dev": {
        "barryvdh/laravel-debugbar": "^3.9.2",
        "barryvdh/laravel-ide-helper": "^2.13",
        "beyondcode/laravel-query-detector": "^1.7",
        "fakerphp/faker": "^1.23.0",
        "laravel/pint": "^1.12",
        "laravel/sail": "^1.24.0",
        "mockery/mockery": "^1.6.6",
        "nunomaduro/collision": "^7.8.1",
        "pestphp/pest": "^2.16.1",
        "pestphp/pest-plugin-laravel": "^2.2",
        "spatie/laravel-ignition": "^2.3"
    },


 "devDependencies": {
        "@tailwindcss/forms": "^0.5.6",
        "alpinejs": "^3.13.0",
        "autoprefixer": "^10.4.15",
        "axios": "^1.5.0",
        "laravel-vite-plugin": "^0.8.0",
        "postcss": "^8.4.29",
        "tailwindcss": "^3.3.3",
        "vite": "^4.4.9"
    }


PS. Every thing has been working up to this point.

avatar

that did not fix a thing It still will not work.

avatar

Did you run npm run dev or prod after removing alpine. Any console errors?

avatar

I am using vite. however I restarted it and I am still getting the same thing and I am getting No errors.

avatar

Please did your find a solution for the issue of $this->reset() not working ? Having the same error

avatar

The reason is in your setup. If you are using breeze then you need to remove alpine and compile assets using npm run prod

avatar

I've got the same issue as Richard A. Hoyle. Everithing works but the reset. I've notice that even the input text content is there, if you try to save again you'll have a message as if the text was empty!

avatar

Read Povilas tweet that was mentioned here already

avatar

Thank you but I'm not using Alpine / Breeze. Have any other sugestion?

{ "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build" }, "devDependencies": { "axios": "^1.1.2", "laravel-vite-plugin": "^0.8.0", "vite": "^4.0.0" } }

avatar

Still it's gonna something about your setup. Maybe you are adding alpine some other way. Hard to tell. Check console errors.

avatar

I start to use Jetstream and all is working now.

Thanks

avatar

Thats one way. But it would be better to find problem which will help in the long

avatar

Thought I would try this out in Jetstream and guess what it is working perfectly the inputs are reset and the info is saved to the database Grate! This is the new Jetstream 4 with livewire 3

avatar

Ots bec it uses the v3 livewire. It means earlier it was problem with how you set up your project. This course isn't about using anything besides livewire

avatar

Following this tutorial, I am stucked at loading the first component. Teacher says "Now, visit the page where your Livewire component should be rendered, and you will see that static text from the Component." We havn't created any route yet so what url should I use in my browser? I am on localhost:8000/....

avatar

OK, I created this route:

Route::get('/post', CreatePost::class);

and a view/components/layout.blade.php: https://laravel.com/docs/10.x/blade#layouts-using-components then changed in the livewire config: 'layout' => 'components.layout', finaly I changed posts/create.blade.php

<x-layout>
    <livewire:create-post />
</x-layout>
avatar

Thanks for the comment. I didn't think I needed to explain the route, as it could be any route that leads to the Blade file, but maybe you're right as it's not perfectly clear.

I added a few lines of explanation with a simple route:

Route::view('posts/create', 'posts.create');
👍 1
avatar

Hey Povilas,

Great course as usual!

Would you consider committing each lesson to the Git repository as you complete it, so that we can follow along more easily lesson-by-lesson? Maybe even add each lesson in several commits, if there are significant stages during the lesson where things change?

Also great "lesson zero" would be setting up the project with commits showing the initial commit of the empty Laravel framework, then subsequent commits showing each package being added, such as TailwindCSS, TailwindCSS/Forms, Livewire 3, etc. Much can be learned quickly on how to create a new project from such a concise first set of commits. And it would mean that we all start out with something which actually looks like your screenshots ;o))

Thanks again.

avatar

I tried to do that, but during the creation of the lesson there are so many little changes that it's extra hard to keep it all in one commit.

The purpose of my lesson is exactly that you would practice on something that does NOT look like my screenshots, as the course is NOT about Laravel/Tailwind, so my goal is that it would fit anyone using Livewire 3, even if they use Bootstrap or other design.

But maybe for the future I would reconsider it, maybe showing one path in full from the beginning, repeating all the fundamentals, is a better way to teach. Will think about it, thanks for the opinion!

avatar

@livewireStyles @livewireScripts didint work form me, the create post from appears without css

avatar

I don't understand why adding these form should be styled. It's livewire styles and v3 doesn't even need it. In this course we are using tailwind for styling.

avatar

Did you used the Tailwind CLI tool or did you include tailwind by using CDN in the header?

avatar

Used breeze to be quicker. But in this course it doesn't matter what you will use