Why does this Blade component not receive a parameter? - laravel

I am using Laravel 9 and trying to understand the components.
I have this 'anonymous' component (extract) :
<option value="{{ $phase->id }}" #if(isset($phase_id) && $phase_id===$phase->id) selected #endif>
{{ $phase->name }}
</option>
The component is called like that :
<x-select-phase id="phase_id" name="phase_id" :phase_id="$event->phase_id"
I am expecting that the var $phase_id exists inside the component. It is not the case, and do not understand why. I tried a lot of things without success. What can I try next?

https://laravel.com/docs/9.x/blade#casing
Component constructor arguments should be specified using camelCase, while kebab-case should be used when referencing the argument names in your HTML attributes.
This means:
:phase-id="$event->phase_id"
should generate a variable:
$phaseId

Related

Passing conditional disabled attribute to blade component with ternary operator

Is there a way to conditionally pass a disabled attribute to a blade component? For example this question and answer mention how to use the ternary operator to pass in the value of the attribute but the attribute name will be there regardless.
I am specifically trying to use the ternary operator in the blade component tag to add (or not add) the disabled attribute
template code:
<x-button {{!$aircraftType->canIslandBuild($island) ? 'disabled' : ''}}>
Build
</x-button>
button component code:
<button {{ $attributes->merge(['class' => 'inline-flex']) }}>
{{ $slot }}
</button>
The error involves adding {{!$aircraftType->canIslandBuild($island) ? 'disabled' : ''}} to the x-button tag.
The error that I'm getting is: syntax error, unexpected token "endif", expecting end of file
Also if I change this {{!$aircraftType->canIslandBuild($island) ? 'disabled' : ''}} to {{''}} the same error happens so I'm curious if you can render strings from php code inside of the component tag header like you can anywhere else in a blade template. I know there are other ways to pass in data and conditionally add the disabled attribute by modifying the button component code but I would like to know if there is an easy solution here.
Went with matiaslauriti's comment and decided to replace
{{!$aircraftType->canIslandBuild($island) ? 'disabled' : ''}}
with
:disabled="!$aircraftType->canIslandBuild($island)"
without changing anything else. It seems disabled="disabled" is included in the $attributes variable only when the value is true. This means that the button renders a disabled="disabled" only when !$aircraftType->canIslandBuild($island) is true and when it is false, no disabled is rendered on the final html for the button.

Problem passing data from one component to another in livewire

Good day, I am passing information from one component to another from a SELECT, when I do not load the information from the json file it works, but when I extract from the json I have the following error.
ErrorException
Trying to get property 'numero_prestamo_original' of non-object (View: C:\laragon\www\prestamos-cth\resources\views\livewire\prestamos\prestamos.blade.php)
The select field:
<option value="2022006799">2022006799</option>
#foreach ($prestamos as $prestamo => $valor)
<option value="{{ $valor->numero_prestamo_original }}">{{ $valor->numero_prestamo_original }}
</option>
#endforeach
Do you have any idea what is happening. thank you very much.
I have seen on the internet to make the change from -> to [ ] but it doesn't work either.
Thank you all for your help, the solution I found for that problem was to do the search with the required fields in the array and only print what is needed. Thanks for your help.

Laravel Livewire wire:click creates infinite loop

I have a Livewire component that's a product filter. The queries all work fine, but sometimes it creates an infinite loop of requests.
You can see that happening in the GIF below which is a capture of the Laravel Debugbar. I'm clicking some filters and then suddenly it goes into this request loop.
I specifically use wire:loading.attr="disabled" on the filters in the view so someone can not select a filter while a request is still processing.
My code and some background:
Livewire Component
use App\Models\Product;
use App\Models\Brand;
use App\Models\Color;
class SearchProducts extends Component
{
public ?array $brand = [];
public ?array $color = [];
protected $queryString = ['brand', 'color'];
public function render()
{
$products = Product::query();
$products = $products->with('brand');
$products = $products->with('colors');
$products = $this->filterBrands($products);
$products = $this->filterColors($products);
$products = $products->paginate(24);
return view('livewire.search-products', [
'all_brands' => Brand::where('status', 'active')->get(),
'all_colors' => Color::where('status', 'active')->get(),
])->extends('app');
}
public function filterBrands($query)
{
$queryFilterBrand = array_filter($this->brand);
return empty($queryFilterBrand) ? $query : $query->whereIn('brand_id', $queryFilterBrand);
}
public function filterColors($query)
{
$queryFilterColor = array_filter($this->color);
return empty($queryFilterColor) ? $query : $query->whereHas('colors', function ($q) use ($queryFilterColor) {
$q->whereIn('color_id', $queryFilterColor);
});
}
}
The reason that I use array_filter is because if I unselect a color value and use a character in the key (wire:model="brand.b{{ $brand->id }}"), instead of removing that from the array Livewire will set that key value to false. So then this false value will be put into the query which will give inaccurate results.
Livewire views and the issue
This works fine:
#foreach($all_brands as $brand)
<input type="checkbox" value="{{ $brand->id }}" id="brand.{{ $brand->id }}" wire:model="brand.{{ $brand->id }}" wire:loading.attr="disabled">
<label class="search-label search-wide-label mb-2" for="brand.{{ $brand->id }}">{{ $brand->title }} <i class="fal fa-times float-right selected-icon"></i></label>
#endforeach
But this creates an infinite loop when I select 2 or more colors after each other, or if I select 1 color and then deselect it. So it seems that issue occurs after the 2nd interaction:
#foreach($all_colors as $color)
<input type="checkbox" value="{{ $color->id }}" id="color.{{ $color->id }}" wire:model="color.{{ $color->id }}" wire:loading.attr="disabled">
<label class="search-label search-wide-label mb-2" for="color.{{ $color->id }}">{{ $color->title }} <i class="fal fa-times float-right selected-icon"></i></label>
#endforeach
This is weird because this blade snippet is exactly the same as for $brands as shown above:
The only thing that different is that the colors relationship is a hasMany vs a belongsTo for brand.
I'm now thinking that this is where the problem is...
The things I've tried and didn't work
Remove the #foreach loop for $all_colors and just put the filters in plain HTML (to check if the issue is related to the loop)
Adding wire:key="brand.{{ $brand->id }}" to the input element
Adding wire:key="brand.{{ $brand->id }}" to a div around the input element
Using wire:model="brand.{{ $brand->id }}" or wire:model="brand.{{ $loop->id }}" as was suggested in the comments (and what I thought solved the problem)
Using wire:model="brand.b{{ $brand->id }}" so there's a unique key name
Removing the array_filter approach (seems unlikely that this is the problem but just to test)
Using buttons instead of checkboxes
Using defer, lazy and/or debounce
Paying an expert to try and fix it...
Console error
Last piece, I get this error in my console only when the infinite loop happens so it's very likely either a cause or effect.
TypeError: null is not an object (evaluating 'directive.value.split')
Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'directive.value.split')
Both in LoadingStates.js which I think is a Livewire Javascript file.
The error there seems to be happening here:
function startLoading(els) {
els.forEach(({ el, directive }) => {
if (directive.modifiers.includes('class')) {
let classes = directive.value.split(' ').filter(Boolean)
Answered on GitHub issue, copied here for others to be able to find.
The problem is a morphdom issue.
The code that is triggering the errors and the loop is the wire:loading on your heading row and products row.
The reason is, when you select two or more colours, there are no results shown. What happens then is you're swapping from showing heading/products/total to showing an empty state.
But morphdom doesn't know by default that it should delete the old divs and add the new one. Instead it is trying to "morph" the old first div into the new one. That means that the wire:loading listeners are still registered when they shouldn't be. Hence why the error and the loop.
It's a simple fix though. You need to add wire keys to the divs defining what they are, so morphdom knows that they have actually changed completely, and to delete the old ones and add new ones.
Have a look at this screenshot below of the diff for what I did to get it working. I added a wire key for all the top level divs inside this file.
It's recommended whenever using conditionals like that to add wire:keys to any elements that are first level inside the conditional, so morphdom knows there has been a change. It's the same problem VueJS has, where keys are required inside loops.
Screenshot of diff
So it turns out that if you're using wire:model in a foreach loop like this, you have to write it like this: wire:model="brand.{{ $brand->id }}". Couldn't find it in the docs so hopefully it helps others here.
Update
The infinite loop is solved by this, but what's happening now is that the array values are set to zero instead of removed from the array once you select a checkbox and then click it again to unselect. So then the whereIn is going to look for brand IDs with value 0 which will not return results.
Update 2
The loop is actually not solved... See original question. Slapping a bounty on this $%$£# because too many hours and coffee were wasted.
A couple issues I see:
You're using the same wire:key for your <div> element and your <input> element. I would keep those unique.
Also, you're using the same name on the input element. This will cause issues with checkboxes as the value passed to the backend has the same name and I believe livewire will attempt to sync based on that. Changing the name field in your input to be unique is likely the answer.

Cannot inject request paraemter into blade view file

I have the following URL:
https://example.com/?email=test#test.com
then I have the following blade template
<input value="{{Request::query('email') or old('email')}}">
But it's not displaying the email I passed in the get parameter into the input. Instead, it's displaying the value 1
Tried searching haven't found a solution that works.
That's because you have {{Request::query('email') or old('email')}} which is conditional. It will always return true or false.
Try any of these:
{{Request::query('email') || old('email')}}
or
{{Request::query('email') ? Request::query('email') : old('email')}}
You can try this:
<input value="{{ request()->email }}">

Display Object list from server in a Select option in Vue

I receive a list of Grades that are in the right format ( $key => $value) in a $grades variable
How can I fill a select option input with Vue using this variable.
I guess I must bind the vue variable with : but I'm just beginning with vue and I can find so very little basic examples,
<select v-model="grades" class="form-control">
<option v-for="gradeValue in gradeValues" :gradeValues="{{ $grades /* Laravel Variable */ }}">#{{ gradeValue }}</option>
</select>
EDIT: I could make an ajax call from Vue, this should not be so complicated, but as the page load, my Laravel controller passes variables to my View, so, this is for me a cleaner aproach, in this case there is no need for ajax.
I think this has been over complicated for you, your code in this question looks close. Is your Vue component in the same file as your blade? Then it's just:
html
<select v-model="selectedGrade" class="form-control">
<option v-for="(grade, val) in grades" :value="val">#{{ grade }}</option>
</select>
js:
new Vue({
...
data:function(){
return {
grades:{{ $grades }},
selectedGrade:2 //some default value
}
}
...
});
If your component is in a separate file you still don't need a separate component for each select dropdown, you just need to pass all the required info into one component. Without a better understanding of your app, I'd guess it could be something like this:
<person-data :grades="{{ $grades }}" :categories="{{ $categories }}" :ages="{{ $ages }}"></person-data>
then the vue component:
var PersonData = Vue.extend({
props:['grades','categories','ages']
});
You have two choices:
You can create the option elements using blade's foreach method:
#foreach ($grades as $grade)
<option ...>{{ $grade }}</option>
#endforeach
Or you can create a dedicated component for your grades with your template and pass in the $grades variable. For example:
<grades :grades="{{ $grades }}"></grades>

Resources