Laracast Livewire Basic Notes
Good for Real time validation, search dropdown, autocomplete, data tables, fileupload
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">
support@example.com
</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('madindo@mailinator.com')->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', 'mad@gmail.com')
->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', 'mad@gmail.com')
->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>