Laracast Livewire Basic Notes

Good for Real time validation, search dropdown, autocomplete, data tables, fileupload

Quickstart | Livewire

composer require livewire/livewire

Making a Counter

php artisan livewire:make Counter

<livewire:counter />

<?php
namespace App\\Http\\Livewire;
use Livewire\\Component;
class Counter extends Component
{
    public $counter = 0;
    public function increment() {
        $this->counter++;
    }
    public function decrement() {
        $this->counter--;
    }
    public function render()
    {
        return view('livewire.counter');
    }
}

<div>
    {{ $counter }}
    <button wire:click="decrement">-</button>
    <button wire:click="increment">+</button>
</div>

Contact Form

php artisan livewire:make ContactForm

<form wire:submit.prevent="submitForm" // prevent submit and use livewire
<input wire:model.lazy="name" // after hover out then will submit
<input wire:model.defer="name" // after submit everything
<button type="button" wire:click="$set('successMessage', null)" // updating a state on html
<svg wire:loading wire:target="submitForm" // will show when it's loading but target that function submitForm

contact-form.blade.php

<div class="relative bg-white mt-8">
    <div class="absolute inset-0">
        <div class="absolute inset-y-0 left-0 w-1/2 bg-gray-50"></div>
    </div>
    <div class="relative max-w-7xl mx-auto lg:grid lg:grid-cols-5">
        <div class="bg-gray-50 py-16 px-4 sm:px-6 lg:col-span-2 lg:px-8 lg:py-24 xl:pr-12">
            <div class="max-w-lg mx-auto">
                <h2 class="text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9">
                    Get in touch
                </h2>
                <p class="mt-3 text-lg leading-6 text-gray-500">
                    Nullam risus blandit ac aliquam justo ipsum. Quam mauris volutpat massa dictumst amet. Sapien tortor
                    lacus arcu.
                </p>
                <dl class="mt-8 text-base leading-6 text-gray-500">
                    <div>
                        <dt class="sr-only">Postal address</dt>
                        <dd>
                            <p>742 Evergreen Terrace</p>
                            <p>Springfield, OR 12345</p>
                        </dd>
                    </div>
                    <div class="mt-6">
                        <dt class="sr-only">Phone number</dt>
                        <dd class="flex">
                            <svg class="flex-shrink-0 h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24"
                                stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                    d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
                            </svg>
                            <span class="ml-3">
                                +1 (555) 123-4567
                            </span>
                        </dd>
                    </div>
                    <div class="mt-3">
                        <dt class="sr-only">Email</dt>
                        <dd class="flex">
                            <svg class="flex-shrink-0 h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24"
                                stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                    d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
                            </svg>
                            <span class="ml-3">
                                [email protected]
                            </span>
                        </dd>
                    </div>
                </dl>
                <p class="mt-6 text-base leading-6 text-gray-500">
                    Looking for careers?
                    <a href="#" class="font-medium text-gray-700 underline">View all job openings</a>.
                </p>
            </div>
        </div>
        <div class="bg-white py-16 px-4 sm:px-6 lg:col-span-3 lg:py-24 lg:px-8 xl:pl-12">
            <div class="max-w-lg mx-auto lg:max-w-none">
                <form wire:submit.prevent="submitForm" action="/contact" method="POST" class="grid grid-cols-1 row-gap-6">
                    @csrf

                    @if ($successMessage)
                    <div class="rounded-md bg-green-50 p-4 mt-8">
                        <div class="flex">
                            <div class="flex-shrink-0">
                                <svg class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd"
                                        d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
                                        clip-rule="evenodd" />
                                </svg>
                            </div>
                            <div class="ml-3">
                                <p class="text-sm leading-5 font-medium text-green-800">
                                    {{ $successMessage }}
                                </p>
                            </div>
                            <div class="ml-auto pl-3">
                                <div class="-mx-1.5 -my-1.5">
                                    <button
                                        type="button"
                                        wire:click="$set('successMessage', null)"
                                        class="inline-flex rounded-md p-1.5 text-green-500 hover:bg-green-100 focus:outline-none focus:bg-green-100 transition ease-in-out duration-150"
                                        aria-label="Dismiss">
                                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                            <path fill-rule="evenodd"
                                                d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
                                                clip-rule="evenodd" />
                                        </svg>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                    @endif

                    <div>
                        <label for="name" class="sr-only">Full name</label>
                        <div class="relative rounded-md shadow-sm">
                            <input wire:model="name" id="name" name="name" value="{{ old('name') }}"
                                class="@error('name')border border-red-500 @enderror form-input block w-full py-3 px-4 placeholder-gray-500 transition ease-in-out duration-150"
                                placeholder="Full name">
                        </div>
                        @error('name')
                        <p class="text-red-500 mt-1">{{ $message }}</p>
                        @enderror

                    </div>
                    <div>
                        <label for="email" class="sr-only">Email</label>
                        <div class="relative rounded-md shadow-sm">
                            <input wire:model="email" id="email" type="text" name="email" value="{{ old('email') }}"
                                class="@error('email')border border-red-500 @enderror form-input block w-full py-3 px-4 placeholder-gray-500 transition ease-in-out duration-150"
                                placeholder="Email">
                        </div>
                        @error('email')
                        <p class="text-red-500 mt-1">{{ $message }}</p>
                        @enderror
                    </div>
                    <div>
                        <label for="phone" class="sr-only">Phone</label>
                        <div class="relative rounded-md shadow-sm">
                            <input wire:model="phone" id="phone" name="phone" value="{{ old('phone') }}"
                                class="@error('phone')border border-red-500 @enderror form-input block w-full py-3 px-4 placeholder-gray-500 transition ease-in-out duration-150"
                                placeholder="Phone">
                        </div>
                        @error('phone')
                        <p class="text-red-500 mt-1">{{ $message }}</p>
                        @enderror
                    </div>
                    <div>
                        <label for="message" class="sr-only">Message</label>
                        <div class="relative rounded-md shadow-sm">
                            <textarea wire:model="message" id="message" rows="4" name="message"
                                class="@error('message')border border-red-500 @enderror form-input block w-full py-3 px-4 placeholder-gray-500 transition ease-in-out duration-150"
                                placeholder="Message">{{ old('message') }}</textarea>
                        </div>
                        @error('message')
                        <p class="text-red-500 mt-1">{{ $message }}</p>
                        @enderror
                    </div>
                    <div class="">
                        <span class="inline-flex rounded-md shadow-sm">
                            <button type="submit"
                                class="inline-flex items-center justify-center py-3 px-6 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out disabled:opacity-50">
                                <svg wire:loading wire:target="submitForm" class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="<http://www.w3.org/2000/svg>" fill="none"
                                    viewBox="0 0 24 24">
                                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                                    <path class="opacity-75" fill="currentColor"
                                        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
                                    </path>
                                </svg>
                                <span>Submit</span>
                            </button>
                        </span>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

ContactForm.php

<?php

namespace App\\Http\\Livewire;

use Livewire\\Component;
use App\\Mail\\ContactFormMailable;
use Illuminate\\Support\\Facades\\Mail;
use Illuminate\\Http\\Request;

class ContactForm extends Component
{
    public $name;
    public $email;
    public $phone;
    public $message;
    public $successMessage;

    protected $rules = [
        'name' => 'required',
        'email' => 'required|email',
        'phone' => 'required',
        'message' => 'required|min:5'
    ];

    public function updated($value) {
        $this->validateOnly($value);
    }

    public function submitForm() {
        $contact = $this->validate();

        Mail::to('[email protected]')->send(new ContactFormMailable($contact));
        $this->resetForm();

        $this->successMessage = 'We received your message successfully and will get back to you shortly';
    }

    private function resetForm() {
        $this->name = '';
        $this->email = '';
        $this->phone = '';
        $this->message = '';
    }

    public function render()
    {
        return view('livewire.contact-form');
    }
}

Testing Contact Form

<?php

namespace Tests\\Feature;

use App\\Http\\Livewire\\ContactForm;
use App\\Mail\\ContactFormMailable;
use Illuminate\\Foundation\\Testing\\RefreshDatabase;
use Illuminate\\Foundation\\Testing\\WithFaker;
use Illuminate\\Support\\Facades\\Mail;
use Livewire\\Livewire;
use Tests\\TestCase;

class ContactFormTest extends TestCase
{
    public function test_main_page_contains_form_livewire_component() {
        $this->get('/')
            ->assertSeeLivewire('contact-form');
    }

    public function test_contact_form_sends_out_an_email() {
        Livewire::test(ContactForm::class)
            ->set('name', 'madindo')
            ->set('email', '[email protected]')
            ->set('phone', '08383843')
            ->set('message', 'test123')
            ->call('submitForm')
            ->assertSee('We received your message successfully and will get back to you shortly');
    }

    public function test_contact_name_form_field_is_required() {
        Livewire::test(ContactForm::class)
            ->set('email', '[email protected]')
            ->set('phone', '08383843')
            ->set('message', 'test123')
            ->call('submitForm')
            ->assertHasErrors(['name' => 'required']);
    }

    public function test_contact_message_form_field_has_minimum_character() {
        Livewire::test(ContactForm::class)
            ->set('message', 'test')
            ->call('submitForm')
            ->assertHasErrors(['message' => 'min']);
    }
}

Search Dropdown

I gotta say it’s so easy now that we have this, remembered when using vue in the old days it was hard

php artisan make:livewire SearchDropdown
<?php

namespace App\\Http\\Livewire;

use Illuminate\\Support\\Facades\\Http;
use Livewire\\Component;

class SearchDropdown extends Component
{

    public $search;
    public $searchResults = [];

    public function updatedSearch($newValue) {
        if (strlen($this->search) < 3) {
            $this->searchResults = [];
        } else {
            $response = Http::get('<https://itunes.apple.com/search?term='.$this->search.'&limit=10>');
            $this->searchResults = $response->json()['results'];
        }
    }

    public function render()
    {
        return view('livewire.search-dropdown');
    }
}
<livewire:search-dropdown />
//search-dropdown.blade.php
<div class="flex-1 flex items-center justify-center px-2 lg:ml-6 lg:justify-end">
    <div class="max-w-lg w-full lg:max-w-xs">
        <label for="search" class="sr-only">Search for songs</label>
        <div class="relative">
            <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                <svg class="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
                    <path fill-rule="evenodd"
                        d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
                        clip-rule="evenodd" />
                </svg>
            </div>
            <input wire:model.debounce.300ms="search"
                id="search"
                class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:border-blue-300 focus:shadow-outline-blue sm:text-sm transition duration-150 ease-in-out"
                placeholder="Search for songs..." type="search" autocomplete="off">
                @if (strlen($search) > 2)
                <ul
                    class="absolute z-50 bg-white border border-gray-300 w-full rounded-md mt-2 text-gray-700 text-sm divide-y divide-gray-200">
                    @forelse ($searchResults as $result)
                        <li>
                            <a
                                @if (array_key_exists('trackViewUrl', $result))
                                    href="{{ $result['trackViewUrl'] }}"
                                @else
                                    href="#"
                                @endif
                                class="flex items-center px-4 py-4 hover:bg-gray-200 transition ease-in-out duration-150">
                                <img src="{{ $result['artworkUrl60'] }}"
                                    alt="album art" class="w-10">
                                <div class="ml-4 leading-tight">
                                    <div class="font-semibold">
                                        @if (array_key_exists('trackName', $result))
                                            {{ $result['trackName'] }}
                                        @else
                                            Untitled
                                        @endif
                                    </div>
                                    <div class="text-gray-600">
                                        @if (array_key_exists('artistName', $result))
                                            {{ $result['artistName'] }}
                                        @else
                                            No Artist
                                        @endif
                                    </div>
                                </div>
                            </a>
                        </li>
                    @empty
                        <li class="px-4 py-4">No results found for "{{ $search }}"</li>
                    @endforelse
                </ul>
                @endif
        </div>
    </div>
</div>

Testing Search Dropdown

php artisan make:test SearchDropdownTest
<?php

namespace Tests\\Feature;

use App\\Http\\Livewire\\SearchDropdown;
use Illuminate\\Foundation\\Testing\\RefreshDatabase;
use Illuminate\\Foundation\\Testing\\WithFaker;
use Livewire\\Livewire;
use Tests\\TestCase;

class SearchDropdownTest extends TestCase
{
    public function test_search_dropdown_searches_correctly(): void
    {
        Livewire::test(SearchDropdown::class)
            ->assertDontSee('John Lennon')
            ->set('search', 'John Lennon')
            ->assertSee('John Lennon');
    }

    public function test_dropdown_show_message_if_no_song_exist(): void
    {
        Livewire::test(SearchDropdown::class)
            ->set('search', 'zxcvzxcvzxcvzxcv')
            ->assertSee('No results found for');
    }
}

Data Tables

php artisan make:livewire DataTables
<?php

namespace App\\Http\\Livewire;

use Livewire\\Component;
use Livewire\\WithPagination;

class DataTables extends Component
{
    use WithPagination;
    public function render()
    {
        return view('livewire.data-tables', [
            'users' => \\App\\Models\\User::paginate(10)
        ]);
    }
}
<div>
    <div class="flex flex-col mt-8">
        <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
            <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                <div class="flex items-center justify-between">
                    <div class="max-w-lg w-full lg:max-w-xs">
                        <label for="search" class="sr-only">Search</label>
                        <div class="relative">
                            <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                                <svg class="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
                                    <path fill-rule="evenodd"
                                        d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
                                        clip-rule="evenodd"></path>
                                </svg>
                            </div>
                            <input id="search"
                                class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:border-blue-300 focus:shadow-outline-blue sm:text-sm transition duration-150 ease-in-out"
                                placeholder="Search" type="search">
                        </div>
                    </div>
                    <div class="relative flex items-start">
                        <div class="flex items-center h-5">
                            <input wire:model="active" id="active" type="checkbox"
                                class="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out">
                        </div>
                        <div class="ml-3 text-sm leading-5">
                            <label for="active" class="font-medium text-gray-700">Active?</label>
                        </div>
                    </div>
                </div>

                <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg mt-4">

                    <table class="min-w-full divide-y divide-gray-200">
                        <thead>
                            <tr>
                                <th
                                    class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                                    Name
                                </th>
                                <th
                                    class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                                    Email
                                </th>
                                <th
                                    class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                                    Status
                                </th>
                                <th class="px-6 py-3 bg-gray-50"></th>
                            </tr>
                        </thead>
                        <tbody class="bg-white divide-y divide-gray-200">
                            @foreach ($users as $user)
                            <tr>
                                <td class="px-6 py-4 whitespace-no-wrap">
                                    <div class="flex items-center">
                                        <div class="flex-shrink-0 h-10 w-10">
                                            <img class="h-10 w-10 rounded-full"
                                                src="<https://www.gravatar.com/avatar/?d=mp&f=y>" alt="">
                                        </div>
                                        <div class="ml-4">
                                            <div class="text-sm leading-5 font-medium text-gray-900">
                                                {{ $user->name }}
                                            </div>
                                        </div>
                                    </div>
                                </td>
                                <td class="px-6 py-4 whitespace-no-wrap">
                                    <div class="text-sm leading-5 text-gray-900">{{ $user->email }}</div>
                                </td>
                                <td class="px-6 py-4 whitespace-no-wrap">
                                    @if ($user->active)
                                    <span
                                        class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
                                        Active
                                    </span>
                                    {{-- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-red-100 text-red-800">
                                                Inactive
                                            </span> --}}
                                    @else
                                    <span
                                        class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-red-100 text-red-800">
                                        Inactive
                                    </span>
                                    @endif
                                </td>
                                <td class="px-6 py-4 whitespace-no-wrap text-right text-sm leading-5 font-medium">
                                    <a href="#" class="text-indigo-600 hover:text-indigo-900">Edit</a>
                                </td>
                            </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>

                <div class="mt-8">
                    {{ $users->links() }}
                </div>
            </div>
        </div>
        <div class="h-96"></div>
    </div>
</div>

Search, Sorting, Active

<?php

namespace App\\Http\\Livewire;

use Livewire\\Component;
use Livewire\\WithPagination;

class DataTables extends Component
{
    use WithPagination;
    public $active = true;
    public $search;
    public $sortField;
    public $sortAsc = true;
    protected $queryString = ['search', 'active', 'sortAsc', 'sortField'];

    public function updatingSearch() {
        $this->resetPage();
    }

    public function sortBy($field) {
        if ($this->sortField == $field) {
            $this->sortAsc = !$this->sortAsc;
        } else {
            $this->sortAsc = true;
        }
        $this->sortField = $field;
    }

    public function render()
    {
        return view('livewire.data-tables', [
            'users' => \\App\\Models\\User::where(function($query) {
                $query->where('name', 'like', '%'.$this->search.'%')
                    ->orWhere('email', 'like', '%'.$this->search.'%');
            })->where('active',$this->active)
            ->when($this->sortField, function($query) {
                $query->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc');
            })
            ->paginate(10)
        ]);
    }
}
<input wire:model="search" id="search"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:border-blue-300 focus:shadow-outline-blue sm:text-sm transition duration-150 ease-in-out" placeholder="Search" type="search">

<input wire:model="active" id="active" type="checkbox" class="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out">

<th
class="px-6 py-3 text-left ">
<div class="flex item-center">
    <button wire:click="sortBy('name')"class="bg-gray-50 text-xs leading-4 font4-medium text-gray-500 uppercase tracking-wider">Name</button>
    <x-sort-icon
        field="name"
        :sortField="$sortField"
        :sortAsc="$sortAsc" />
</div>
</th>
<th
class="px-6 py-3 text-left">
<div class="flex item-center">
    <button wire:click="sortBy('email')"class="bg-gray-50 text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Email</button>
    <x-sort-icon
        field="name"
        :sortField="$sortField"
        :sortAsc="$sortAsc" />
</div>
</th>
php artisan make:component SortIcon
<?php

namespace App\\View\\Components;

use Closure;
use Illuminate\\Contracts\\View\\View;
use Illuminate\\View\\Component;

class SortIcon extends Component
{
    public $field;
    public $sortAsc;
    public $sortField;
    /**
     * Create a new component instance.
     */
    public function __construct($field, $sortField, $sortAsc)
    {
        $this->field = $field;
        $this->sortField = $sortField;
        $this->sortAsc = $sortAsc;
    }

    /**
     * Get the view / contents that represent the component.
     */
    public function render(): View|Closure|string
    {
        return view('components.sort-icon');
    }
}

Comments

php artisan livewire:make CommentsSection
<?php

namespace App\\Http\\Livewire;

use Livewire\\Component;
use App\\Models\\Post;
use App\\Models\\Comment;
class CommentsSection extends Component
{
    public $successMessage;
    public $comment;
    public $post;

    protected $rules = [
        'comment' => 'required|min:4',
        'post' => 'required',
    ];

    public function mount(Post $post) {
        $this->post = $post;
    }

    public function postComment() {
        $this->validate();
        Comment::create([
            'post_id' => $this->post->id,
            'username' => 'Guest',
            'content' => $this->comment
        ]);

        $this->comment = '';
        $this->post = Post::find($this->post->id);

        $this->successMessage = 'Comment was posted';
    }

    public function render()
    {
        return view('livewire.comments-section');
    }
}
//views/livewire/comments-section.blade.php
<form wire:submit.prevent="postComment" action="#" method="POST" class="w-1/2 my-12">
        @csrf
        <div class="flex">
            <img class="h-10 w-10 rounded-full" src="<https://www.gravatar.com/avatar/?d=mp&f=y>" alt="avatar">
            <div class="ml-4 flex-1">
                <textarea wire:model.defer="comment" name="comment" id="comment" rows="4" placeholder="Type your comment here..."
                    class="border rounded-md shadow w-full px-4 py-2"></textarea>

                @error('comment')
                <p class="text-red-500 mt-1">{{ $message }}</p>
                @enderror

                <button type="submit"
                    class="inline-flex items-center px-4 py-2 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150 mt-2 disabled:opacity-50">
                    <svg wire:loading wire:target="postComment" class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
                        xmlns="<http://www.w3.org/2000/svg>" fill="none" viewBox="0 0 24 24">
                        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                        <path class="opacity-75" fill="currentColor"
                            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
                        </path>
                    </svg>
                    <span>Post Comment</span>
                </button>

            </div>
        </div>
    </form>

Polling

Livewire offers a directive called wire:poll that, when added to an element, will refresh the component every 2s.

<livewire:poll-example />
<div wire:poll.1000ms>
    Current time: {{ now() }}
</div>

Example showing revenue

<div wire:poll.1s="getRevenue">
    Revenue: ${{ $revenue }}
</div>
<?php

namespace App\\Http\\Livewire;

use Livewire\\Component;

class PollExample extends Component
{
    public $revenue;

    public function mount() {
        $this->revenue = $this->getRevenue();
    }

    public function getRevenue() {
        $this->revenue = \\DB::table('orders')->sum('price');
        return $this->revenue;
    }

    public function render()
    {
        return view('livewire.poll-example');
    }
}

File Uploads

php artisan make:livewire PostEdit
<?php

namespace App\\Http\\Livewire;

use Livewire\\Component;
use App\\Models\\Post;
use Livewire\\WithFileUploads;

class PostEdit extends Component
{
    use WithFileUploads;

    public $successMessage = '';
    public $post;
    public $title;
    public $content;
    public $photo;

    protected $rules = [
        'title' => 'required',
        'content' => 'required',
        'photo' => 'nullable|sometimes|image|max:5000',
    ];

    public function submitForm(){
        $this->validate();

        $this->post->update([
            'title' => $this->title,
            'content' => $this->content,
            'photo' => $this->photo ? $this->photo->store('photos', 'public') : null,
        ]);

        session()->flash('success_message','Post was updated successfully!');
    }

    public function mount(Post $post) {
        $this->post = $post;
        $this->title = $post->title;
        $this->content = $post->content;
    }
    public function render()
    {
        return view('livewire.post-edit');
    }
}
<h2 class="text-lg font-semibold mt-4">Polling Example</h2>
<livewire:poll-example />
<form wire:submit.prevent="submitForm" action="#" method="POST" enctype="multipart/form-data">
    @csrf
    @method('PATCH')
    <div>
        <div>
            <div>
                <a href="/" class="text-blue-600">Back to main page</a>
                <h3 class="text-lg leading-6 font-medium text-gray-900 mt-2">
                    Edit Post
                </h3>
                <p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
                    You can edit your post here.
                </p>
                @if ($successMessage)
                <div class="rounded-md bg-green-50 p-4 mt-8">
                    <div class="flex">
                        <div class="flex-shrink-0">
                            <svg class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
                                <path fill-rule="evenodd"
                                    d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
                                    clip-rule="evenodd" />
                            </svg>
                        </div>
                        <div class="ml-3">
                            <p class="text-sm leading-5 font-medium text-green-800">
                                {{ $successMessage }}
                            </p>
                        </div>
                        <div class="ml-auto pl-3">
                            <div class="-mx-1.5 -my-1.5">
                                <button type="button" wire:click="$set('successMessage', null)"
                                    class="inline-flex rounded-md p-1.5 text-green-500 hover:bg-green-100 focus:outline-none focus:bg-green-100 transition ease-in-out duration-150"
                                    aria-label="Dismiss">
                                    <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                        <path fill-rule="evenodd"
                                            d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
                                            clip-rule="evenodd" />
                                    </svg>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
                @endif
            </div>
            <div class="mt-6 sm:mt-5">
                <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
                    <label for="title" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
                        Title
                    </label>
                    <div class="mt-1 sm:mt-0 sm:col-span-2">
                        <div class="max-w-lg rounded-md shadow-sm sm:max-w-xs">
                            <input wire:model="title" id="title" name="title"
                                class="form-input block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
                                value="{{ $post->title }}">
                            @error('title')
                            <p class="text-red-500 mt-1">{{ $message }}</p>
                            @enderror
                        </div>
                    </div>
                </div>

                <div
                    class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
                    <label for="content" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
                        Content
                    </label>
                    <div class="mt-1 sm:mt-0 sm:col-span-2">
                        <div class="max-w-lg flex rounded-md shadow-sm">
                            <textarea wire:model="content" id="content" name="content" rows="5"
                                class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5">{{ $post->content }}</textarea>
                        </div>
                        @error('content')
                        <p class="text-red-500 mt-1">{{ $message }}</p>
                        @enderror
                    </div>
                </div>

                <div
                    class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
                    <label for="photo" class="block text-sm leading-5 font-medium text-gray-700 sm:mt-px sm:pt-2">
                        Cover Photo
                    </label>
                    <div
                        class="mt-2 sm:mt-0 sm:col-span-2"
                        x-data="{ isUploading: false, progress: 0 }"
                        x-on:livewire-upload-start="isUploading = true"
                        x-on:livewire-upload-finish="isUploading = false"
                        x-on:livewire-upload-error="isUploading = false"
                        x-on:livewire-upload-progress="progress = $event.detail.progress"
                    >
                        <input wire:model="photo" type="file" name="photo">
                        @error('photo')
                        <p class="text-red-500 mt-1">{{ $message }}</p>
                        @enderror

                        <!-- Progress Bar -->
                        <div class="mt-4" x-show="isUploading">
                            <progress max="100" x-bind:value="progress"></progress>
                        </div>

                        <div>
                            <svg wire:loading wire:target="photo" class="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-600"
                                xmlns="<http://www.w3.org/2000/svg>" fill="none" viewBox="0 0 24 24">
                                <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                                <path class="opacity-75" fill="currentColor"
                                    d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
                                </path>
                            </svg>
                        </div>

                        <div class="mt-4">
                            @if ($photo)
                                <img src="{{ $photo->temporaryUrl() }}" alt="temp">
                            @elseif ($post->photo)
                                <img src="{{ Storage::url($post->photo) }}" alt="cover image">
                            @endif
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="mt-8 border-t border-gray-200 pt-5">
        <div class="flex justify-end">
            <span class="ml-3 inline-flex rounded-md shadow-sm">
                <button type="submit"
                    class="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out">
                    Update
                </button>
            </span>
        </div>
    </div>
</form>

Subscribe to You Live What You Learn

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe