property does not refresh in the internal components of Livewire - laravel

Take a look at the following examples:
showPost.blade.php:
<div>
<livewire:content-box :content="$post"/>
<button wire:click="nextPost" >Next Post >></button>
</div>
and
content-box.blade.php :
<div>
<h1>{{ $content->title }}</h1>
<p>{{ $content->content }}</p>
</div>
So far, it is completely clear what is going to happen ...: First, the information of the content to be viewed is received through showPost and passed to the contentBox, and everything is OK ..
Well now I want to get the information of the next content via the account through the button I put and calling the nextPost method:
class ShowPost extends Component
{
public Post $post;
public function render()
{
return view('livewire.show-post');
}
public function nextPost()
{
$id = $this->post->id;
$nextPost = Post::where('id', '>', $id)->first();
$this->post = $nextPost;
}
...
But nothing happens and the contentBox component has no reaction .... Has anyone had this problem ???!

I'm not sure livewire works well with nested components. could use the pagination instead. The livewire docs suggest you should not use them for little snippets or use blade components for that kind of nesting.
You can achieve what you're doing at the moment with some simple pagination.
<?php
namespace App\Http\Livewire;
use App\Models\User;
use Livewire\Component;
use Livewire\WithPagination;
class SomeContent extends Component
{
use WithPagination;
public function render()
{
// Using simplePaginate(1) instead of paginate(1).
// simplePaginate only shows "<- Previous" and "Next ->" links
// paginate shows those 2 buttons but also page numbers which you don't seem to want.
return view('livewire.some-content', [
'users' => User::simplePaginate(1),
]);
}
}
<div>
{{-- This might look wrong, but essentially it's looping through an array of length 1 because we're paginating --}}
#foreach ($users as $user)
<h1>{{ $user->name }}</h1>
<h2>{{ $user->email }}</h2>
#endforeach
{!! $users->links() !!}
</div>
EDIT
I can confirm blade components work.
Here, nextUser is the same implementation you gave.
public function nextUser()
{
$id = $this->user->id;
$nextUser = User::where('id', '>', $id)->first();
$this->user = $nextUser;
}
<div class="container">
<div class="content">
{{-- These two have the exact same template --}}
<livewire:child :user="$user" />{{-- Doesn't update when clicking Next --}}
<x-blade-child :user="$user" />{{-- Updates when clicking Next --}}
</div>
<div>
<button wire:click="nextUser">Next</button>
</div>
</div>
When clicking nextUser, the blade component updates but the livewire one doesn't.

Livewire doesn't like nested components. In your case, we can use basic blade component:
<div>
<x-content-box :content="$post"/>
<button wire:click="nextPost" >Next Post >></button>
</div>
And then:
Move content-box.blade.php to resources/views/components/
Remove component_name.php file in app/Http/Livewire
Most of the time, we can change 2 nested livewire components to livewire(parent) + basic blade component(child),

Related

Laravel Livewire form, if validation fails, pivots don't work

I have a form made with Livewire in Laravel.
This is the Livewire controller
namespace App\Http\Livewire;
use Livewire\Component;
use App\Rules\Mobile;
class Form extends Component
{
public $mobile;
public $required;
public $fields;
public $showDropdown = true;
public function mount()
{
foreach($this->fields as $field){
$this->required[$field->name] = ($field->pivot->is_required == '1') ? 'required' : 'nullable';
}
}
public function submit()
{
$validatedData = $this->validate([
'mobile' => [$this->required['mobile'] ?? 'nullable', new Mobile()]
]);
}
}
This is the Livewire view
<div>
<div x-data="{ open: #entangle('showDropdown').defer, required: #js($required) }">
<span x-show="open" wire:loading.remove>
<form wire:submit.prevent="submit" style="display: flex; flex-direction: column;">
<div class="fields">
#foreach($fields as $field)
<div class="form-{{$field->name}}">
#if($field->name == 'mobile')
<input name="{{$field->name}}" type="tel" wire:model.defer="{{ $field->name }}" placeholder="{{ __($field->pivot->placeholder ?? $field->name) }}">
#endif
#error($field->name) <span class="error">{{ ucfirst($message) }}</span> #enderror
</div>
#endforeach
</div>
<button class="btn btn-primary">Send</button>
</form>
</span>
</div>
</div>
Problem is here placeholder="{{ __($field->pivot->placeholder ?? $field->name) }}"
When the form is first loaded $field->pivot->placeholder is set, but after I submit the form and the validation fails, it's not set anymore and $field->name is used instead of $field->pivot->placeholder
Have checked this way:
<input name="{{$field->name}}" type="tel" wire:model.defer="{{ $field->name }}" placeholder="{{ __($field->pivot->placeholder ?? $field->name) }}">
{{ var_dump(isset($field->pivot->placeholder)) }}
When the form is first loaded it prints bool(true) under the field, after I send the form it says bool(false)
How can I get over this? Why the pivot does not work after validation fails?
//Edit: Did a workaround, but I would still like to know why it happens
What I did is I used another property $placeholders
In the Livewire controller have added public $placeholders;, in the mount() have added $this->placeholders[$field->name] = $field->pivot->placeholder ?? $field->name;, and then used it in the view like placeholder="{{ __($placeholders[$field->name] ?? $field->name) }}"
I don't know much about livewire , but I know that mount() only called on the initial page load meaning it will only run when you refresh the whole page or visit the page. Instead of only using mount() you should also use hydrate() which will run every subsequent request after the page load.
mount() - Initial Page Load
hydrate() - Every subsequent request
You can implement the hydrate() like how you implement the mount()

How to change the number of links in the pagination block? (Laravel 8)

I use Laravel Framework 8 (8.83.23). Please help me understand how to set a limit on the number of links.
I have this (custom template in views/vendor/pagination):
And want to get this:
Already tried this:
/** In controller */
public function index()
{
return view('pages.products.index')
->with(
'products',
Product::latest()
->paginate(12)
->onEachSide(2)
);
}
And this (without using onEachSide in controller):
/** In blade */
<div class="row py-5">
<div class="col-auto mx-auto">
{{ $products->onEachSide(2)->links() }}
</div>
</div>

Showing data from database using Laravel Eloquent Model

So, I'm quite new to Laravel and what I am trying to achieve is to display some string from my data to a page. I'm using Eloquent Model and I can't figure out what I do wrong here. I've attached some of my code below. I hope I make it clear enough.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Product;
class ProductsController extends Controller
{
public function index()
{
$products = Product::all();
return view('products.index')->with('products', $products);
}
Here's where I want the data to be displayed.
#extends ('layouts.app')
#section ('content')
<div class="container-fluid">
<h1>Products</h1>
#if(count($products) > 1 )
#foreach ($products as $product)
<div class="well">
<h3>{{$product->prod_name}}</h3>
</div>
#endforeach
#else
<p>No product found</p>
#endif
</div>
#endsection
UPDATED: The problem is with my loop logic
Since I have only one item inside my database, my loop is supposed to be >= 1 instead of > 1
#if(count($products) >= 1 )
#foreach ($products as $product)
<div class="well">
<h3>{{$product->prod_name}}</h3>
</div>
#endforeach
#else
<p>No product found</p>
#endif
Let's fix up your code and use the correct approach.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Product;
class ProductsController extends Controller
{
public function index()
{
$products = Product::all();
return view('products.index', compact('products'));
}
Notice how I've passed an array via compact as the 2nd parameter of the view function. You can also use view('products.index', ['products' => $products]), I just prefer compact as it is cleaner.
#extends('layouts.app')
#section('content')
<div class="container-fluid">
<h1>Products</h1>
#if(!$products->isEmpty())
#foreach($products as $product)
<div class="well">
<h3>{{ $product->prod_name }}</h3>
</div>
#endforeach
#else
<p>No products found.</p>
#endif
</div>
#endsection
Notice how I've made use of isEmpty, which checks if a collection (obtained here from Eloquent) is empty or not.
Instead of with() maybe View::share can work.
$products = Product::all();
View::share('$products',$products);
return view('products.index');
And dont forget to import View class from Facades. Put this at the begining of page
use Illuminate\Support\Facades\View;

laravel rendering section no passing parameters data

i have a problem passing variable during the rendering of only a section
All works good but array of data passed to the section('sidebar') view create an error ($data doesn't exist)
My blade files are
Default.blade.php
..other html code before..
<body>
#include('includes.header')
<div class="container-fluid">
<div class="row">
#yield('sidebar')
<!-- main content -->
#include('includes.main')
</div>
<footer class="row">
#include('includes.footer')
</footer>
</div>
</body>
..other code after..
home.blade.php
#extends('layouts.default')
#section('sidebar')
#include('includes.sidebar')
#stop
sidebar.blade.php
..other html code before..
<h2>The current UNIX timestamp is {{ time() }}.</h2>
<ul>
#isset($data)
#foreach ($data as $item)
<li class="nav-item"> {{$item->polizza}}</li>
#endforeach
#endisset
</ul>
..other html code after..
Controller Method search
public function search(Request $request){
if ($request->ajax()) {
$data = Customers::select('id','contr_nom','email','targa','cliente')
->where('polizza',request('polizza'))
->get();
return view('pages.home',$data)->renderSections()['sidebar'];
}
//return json_encode($data);
}
I know that array $data is good because i try return just JSON and i know that just sidebar refresh because timestamp change.
But $data is not passed to sidebar section refreshed!!
Why?
Thks a lot
You have the right idea, you just need to send the variable in a form that will be recognized. I'll break it out to an extreme, to help understand the parts, but you can easily recombine for shorter code.
$view = view('pages.home', compact('data')); // Compact with the text name ('data') sends the variable through
$sections = $view->renderSections(); // returns an associative array of 'content', 'pageHeading' etc
return $sections['sidebar']; // this will only return whats in the sidebar section of the view
Using compact() should get your where you need, and is the key.

Undefined variable Laravel (strange case)

I am m having this annoying problem. Can anyone give a hand to sort out it? I read all posts and I cannot find the solution.
This is my controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use DB;
use App\Post;
use App\RegisteredCourse;
class DashboardsController extends Controller
{
public function indexA()
{
return view('dashboards.admin-dashboard');
}
public function indexS()
{
$id = Auth::user()->id;
$courses = DB::table('registered__courses')->select('user_id')->where('user_id', '=', $id)->count();
$posts = Post::all();
return view('dashboards.student-dashboard', compact('id', 'courses', 'posts'));
}
public function indexT()
{
return view('dashboards.teacher-dashboard');
}
}
This is a fragment of the blade view. The name of the view is dashboards.student-dashboard
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Posts</h3>
</div>
<div class="card-body">
<div class="tab-content">
<div class="active tab-pane" id="activity">
<!-- Post -->
<!--forEACH-->
#foreach ($posts as $post)
<div class="post">
<div class="user-block">
<span class="username">
{{$post->title}} | {{$post->author}}
</span>
<span class="description">{{optional($post->created_at)->format('d-m-Y')}}</span>
</div>
<!-- /.user-block -->
<p>
{{$post->content}}
</p>
</div>
#endforeach
</div>
</div>
</div>
</div>
</div>
And these are the routes
Route::group(['prefix' => 'dashboard',], function () {
Route::get('/admin', 'DashboardsController#indexA')->name('dashboards.indexA');
Route::get('/teacher', 'DashboardsController#indexT')->name('dashboards.indexT');
Route::get('/student', 'DashboardsController#indexS')->name('dashboards.indexS');
});
I tried to pass al the variable to the view and I always have the same problem. ("Undefined variable").It seems like blocked the possibility to pass variable to the blade view.
What I did before:
1-dd($posts) It does not working, it appears the exception "Undefined variable: posts".
2- I remove the whole content of the blade file and I tried with a simple varible and it stills appearing the exception "Undefined variable".
3- I ran php artisan view:clear and restarted the server.
Any sugestions?
Thank you very much.
Instead of
return view('dashboards.student-dashboard', compact('id', 'courses', 'posts'));
try
return view('dashboards.student-dashboard', ['id'=>$id, 'courses'=>$courses, 'posts'=>$posts]);

Resources