Avoid mutating a prop directly for #click - laravel

I'm trying to import and use the JetButton component from laravel jetstream. I've added the #click emit event to the jetbutton but getting the error:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
Component
<template>
<jet-button #click="update()">
Update
</jet-button>
</template>
<script>
method:{
update(){ //doing something }
}
</script>
JetButton
<template>
<button #click="$emit('click', $event);" :type="type" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150">
<slot></slot>
</button>
</template>

When emitting an event you need to define the method first which will be called and then emit that event from the method.
The error tells that, don't emit event directly in the prop.
On #click call a method defined in the JetButton component and then use this.$emit('call').
Component
<template>
<jet-button #call="update">
Update
</jet-button>
</template>
<script>
methods () {
update(){ //doing something }
}
</script>
JetButton
<template>
<button #click="call" :type="type" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150">
<slot></slot>
</button>
</template>
<script>
methods () {
call () {
this.$emit('call')
}
}
</script>
Handle the event call in the component as <jet-button #call="update"> and then define that method in the methods() section.

Related

List recursive with checkbox component data into parent component update values

Help to list recursive with checkbox component data into parent component update values:
Parent File:
<TreeVue :items="props.allModules" :selectedItems="userForm.userModules"/>
TreeVue File:
<template>
<div v-for="item in items" :key="item.name" class="overflow-hidden rounded-lg bg-gray-50">
<div class="px-4 py-5 sm:p-6" >
<div class="flex items-center ">
<input :id="item.id" type="checkbox" :value="item.id" v-model="props.selectedItems" class="w-6 h-6 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<label for="default-checkbox" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">{{ item.name }}</label>
</div>
<div v-if="item.children" class="ml-5">
<Tree :items="item.children" :selectedItems="selectedItems" />
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
const props = defineProps({
items: Object,
selectedItems: Object,
});
</script>
ConsoleLog:
app.js:7839 [Vue warn] Set operation on key "selectedItems" failed: target is readonly. Proxy {items: Proxy, selectedItems: Proxy}
I need to update parent value after selected item on child TreeVue.
I read something about Emit, but I don't know how to implement it, because I'm new to Vue.

nest and previous pagination inside div

So, I have created a Laravel controller with pagination. The code inside my Vue file is the following
<div class="flex justify-between flex-wrap">
<div v-for="kink in kinks.data" :key="kink.id" :value="kink.name">
<button type="button" class="m-1 my-2 inline-flex items-center px-3.5 py-2 border border-transparent text-sm leading-4 font-medium rounded-full shadow-sm text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> {{ kink.name }}</button>
</div>
</div>
<div class="flex justify-between">
<ArrowLeftIcon type="button" :href="kinks.next_page_url" class="m-2 inline-flex items-center px-2 py-1 h-7 rounded-full border border-transparent text-sm leading-4 font-medium shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 "></ArrowLeftIcon>
<ArrowRightIcon type="button" :href="kinks.last_page_url" class="m-2 inline-flex items-center px-2 py-1 h-7 rounded-full border border-transparent text-sm leading-4 font-medium shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"></ArrowRightIcon>
</div>
The arrow buttons should change the v-for data based on the active page. I have NEVER done this before and I do not want to use some cheap npm package when I just need next and previous.
IDEALLY, if someone is on page 1, and they click previous, it should go to the last page and the same with last page next should go to page 1
How do you implement this?
The data object inside vue from the pagination provides the following:
kinks:Object
current_page:1
data:Array[13]
first_page_url:"http://localhost:8000/kinks?page=1"
from:1
last_page:5
last_page_url:"http://localhost:8000/kinks?page=5"
links:Array[7]
next_page_url:"http://localhost:8000/kinks?page=2"
path:"http://localhost:8000/kinks"
per_page:13
prev_page_url:null
to:13
total:63
ok so you will want to change the way your buttons work then. e.g. add a #click method to them instead of a href
example
<button #click="previousLink">
...icon
</button>
<script>
export default {
methods: {
previousLink() {
if (this.prev_page_url === null) {
window.location.href = this.last_page_url;
} else {
window.location.href = this.prev_page_url;
}
}
}
}
</script>

Laravel + Inertia + Vuejs: Pagination

When I get all the records it works:
...
$items = Item::all();
return Inertia::render('Rentals/Items', ['items' => $items]
);
But when I try to paginate, it breaks down:
...
$items = Item::paginate(15);
return Inertia::render('Rentals/Items', ['items' => $items]
);
I get this error:
Uncaught (in promise) TypeError: Cannot read property 'id' of null
Help please. What am I doing wrong? I've been stuck for days.
Creator of Inertia.js here. 👋
So, you can totally use the Laravel paginator with Inertia, you just need to setup your page components to be compatible.
First, make sure you're only returning the items data to the client that you actually need. You can use the new pagination through method for this.
$items = Item::paginate(15)->through(function ($item) {
return [
'id' => $item->id,
'name' => $item->name,
// etc
];
});
return Inertia::render('Rentals/Items', ['items' => $items]);
Next, client side, you'll need a pagination component to actually display the pagination links. Here is an example component from the Ping CRM demo app, built using Tailwind CSS.
<template>
<div v-if="links.length > 3">
<div class="flex flex-wrap -mb-1">
<template v-for="(link, key) in links">
<div v-if="link.url === null" :key="key" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded" v-html="link.label" />
<inertia-link v-else :key="key" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500" :class="{ 'bg-white': link.active }" :href="link.url" v-html="link.label" />
</template>
</div>
</div>
</template>
<script>
import {InertiaLink} from "#inertiajs/inertia-vue3";
export default {
props: {
links: Array,
},
components : {
InertiaLink
}
}
</script>
Finally, to display the items and the pagination links in your page component, use the items.data and items.links props. Something like this:
<template>
<div>
<div v-for="item in items.data" :key="item.id">
{{ item.name }}
</div>
<pagination :links="items.links" />
</div>
</template>
<script>
import Pagination from '#/Shared/Pagination'
export default {
components: {
Pagination,
},
props: {
items: Object,
},
}
</script>
You can find a full working example of this in the Ping CRM demo app. 👍
Just to keep this post updated I'm adding what works for me using vue 3, laravel 8 and inertia 0.10:
In Pagination component I must change the :key attribute just as #AdiMadhava said before, in addition I must use Link component in order to make the page links usables, so my entire #/Shared/Pagination.vue looks like:
<template>
<div v-if="links.length > 3">
<div class="flex flex-wrap mt-8">
<template v-for="(link, key) in links" :key="key">
<div
v-if="link.url === null"
class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded"
v-html="link.label"
/>
<Link
v-else
class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-white focus:border-primary focus:text-primary"
:class="{ 'bg-white': link.active }"
:href="link.url"
v-html="link.label"
/>
</template>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { Link } from "#inertiajs/inertia-vue3";
export default defineComponent({
components: {
Link,
},
props: {
links: Array,
},
});
</script>
And it works accurately.

Laravel and Livewire change button style on click

I have two buttons in tailwindcss in my
<div class="flex flex-row">
<button class="-ml-px relative inline-flex items-center space-x-2 px-4 py-4 border border-blue-300 text-sm font-medium rounded-l-md text-white bg-blue-500 hover:bg-blue-600 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-blue-500">
<span>Add</span>
</button>
<button class="-ml-px relative inline-flex items-center space-x-2 px-4 py-4 border border-gray-300 text-sm font-medium text-gray-900 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-blue-500">
<span>Set</span>
</button>
</div>
one button is blue and other is grey.
I want to switch colour when I click on Set Button and also same when I click back to Add button again.
As I mentioned, Livewire is for interaction with backend code. If you want to style frontend elements following frontend interaction, use a JS Framework like AlpineJS or plain CSS.
If you really just want to change focus color, you can go with a variation of #Digvijay's answer:
<div class="flex space-x-4">
<button class="flex px-4 py-2 bg-gray-100 text-gray-900 cursor-pointer hover:bg-blue-200 focus:text-blue-700 focus:bg-blue-200 focus:outline-none focus:ring-blue-600" tabindex="1">Add</button>
<button class="flex px-4 py-2 bg-gray-100 text-gray-900 cursor-pointer hover:bg-blue-200 focus:text-blue-700 focus:bg-blue-200 focus:outline-none focus:ring-gray-600" tabindex="2">Set</button>
</div>
(see https://play.tailwindcss.com/mwspfpsTuU)
If you want the colors to stick even after the focus is lost, you may use something like this with AlpineJS:
<div x-data="{ highlightedButton: '' }" class="flex space-x-4">
<button #click="highlightedButton='add'" class="flex px-4 py-2 bg-gray-100 text-gray-900 hover:bg-blue-200" :class="{'bg-blue-400': highlightedButton === 'add'}" tabindex="1">Add</button>
<button #click="highlightedButton='set'" class="flex px-4 py-2 bg-gray-100 text-gray-900 hover:bg-blue-200" :class="{'bg-blue-400': highlightedButton === 'set'}" tabindex="2">Set</button>
</div>
And finally if you keep track of the Add / Set state in the Livewire component anyways (which is hard to tell because there is no livewire code in your markup at all), then do it like #AliAli described in his answer. :-)
Edit:
If anyone came to this answer, this is a solution when storing the button state is needed, in other cases, you should follow #Lupinity Labs answer.
You can use Livewire event listeners to change the frontend.
First, declare variables inside the Livewire PHP model like:
public $isSetClicked = false;
public $isAddClicked = false;
Then decalre functions to handle the onClick event:
public function SetClicked()
{
//this will give you toggling behavior
$this->isSetClicked == false ? $this->isSetClicked = true : $this->isSetClicked = false;
}
and declare the AddClicked the same way.
Then inside your Livewire template add the events to the buttons like:
<button wire:click="SetClicked()" class="{{$isSetClicked ? "color-class" : "other-color-class"}} the rest of your css classes">
To toggle HTML attributes with livewire, for example class, id, href and so on you have to work with AlpineJS inside livewire (docs). For example you want to change some element CSS class attribute by changing a some internal state (model) inside your livewire component. For example by clicking a some link.
/app/Http/Livewire/YourLivewireComponent.php
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class YourLivewireComponent extends Component
{
public $classChanged = false;
public function render()
{
return view('livewire.your-liveweire-component');
}
public function myClickFunction()
{
$this->classChanged = true;
}
}
/resources/views/livewire/your-livewire-component.blade.php
Click me
<div x-data="{ alpine_class_changed: #entangle('classChanged') }" class="some classes" x-bind:class="alpine_class_changed ? 'my_add_class' : ''">some text</div>
/resources/views/somelaraveltemplate.blade.php
<p>some HTML code</p>
#livewire('your-liveweire-component')
You will need to use tabindex. Checkout focus change on button working example.
<div class="flex space-x-4">
<div class="flex px-4 py-2 bg-blue-600 text-blue-100 cursor-pointer hover:bg-blue-700 focus:text-blue-700 focus:bg-blue-200 focus:outline-none focus:ring-blue-600" tabindex="1">Add</div>
<div class="flex px-4 py-2 bg-gray-100 text-gray-600 cursor-pointer hover:bg-gray-200 focus:text-gray-100 focus:bg-gray-600 focus:outline-none focus:ring-gray-600" tabindex="1">Set</div>
</div>

Can't access component method inside my blade file

I am trying to access a method inside my blade file, with #click but I am getting an error
signup-modal.vue
<template>
<div>
<div v-if="modal" class="animated fadeIn fixed z-50 pin overflow-auto bg-black flex">
<div class="animated fadeInUp fixed shadow-inner max-w-md md:relative pin-b pin-x align-top m-auto justify-end md:justify-center p-8 bg-white md:rounded w-full md:h-auto md:shadow flex flex-col">
<p class="text-xl leading-normal mb-8 text-center">
{{ __("Sign up to name to start socializing") }}
</p>
<div class="justify-center">
<form action="/auth/register" method="post">
<div class="mb-4">
<label class="block text-grey-darker text-sm font-bold mb-2" for="username">
{{ __("Name" )}}
</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight" id="username" type="text" placeholder="Username">
</div>
<div class="mb-4">
<label class="block text-grey-darker text-sm font-bold mb-2" for="username">
{{ __("Email" )}}
</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight" id="username" type="text" placeholder="Username">
</div>
<div class="mb-4">
<label class="block text-grey-darker text-sm font-bold mb-2" for="username">
{{ __("Password" )}}
</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight" id="username" type="text" placeholder="Username">
</div>
<div class="flex items-center justify-end">
<button class="bg-teal hover:bg-teal-dark text-white font-bold py-2 px-4 rounded" type="button">
{{ __("Sign up") }}
</button>
</div>
</form>
</div>
<span #click="toggleModal" class="absolute pin-t pin-r pt-4 px-4">
<svg class="h-6 w-6 text-grey hover:text-grey-darkest" role="button" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><title>Close</title><path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/></svg>
</span>
</div>
</div>
</div>
</template>
<script>
export default {
data: function()
{
return {
modal: false
}
},
methods: {
toggleModal: function() {
console.log("djdjd")
//this.modal = !this.modal
}
}
}
</script>
app.js
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
Vue.component('signup-modal', require('./components/signup-modal.vue'));
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app'
});
My blade file:
<div id="app">
<button class="bg-blue w-full rounded-full text-white py-2 mb-2" #click="toggleModal">
{{ __("Sign up") }}
</button>
</div>
<div>
<signup-modal></signup-modal>
</div>
</div>
As you can see in my blade file I am trying to access my components method toggleModal but I am getting an error:
app.js:36520 [Vue warn]: Property or method "toggleModal" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
(found in <Root>)
warn # app.js:36520
warnNonPresent # app.js:37826
has # app.js:37859
(anonymous) # VM374:3
Vue._render # app.js:40471
updateComponent # app.js:38715
get # app.js:39069
Watcher # app.js:39058
mountComponent # app.js:38722
Vue.$mount # app.js:44467
Vue.$mount # app.js:46866
Vue._init # app.js:40567
Vue # app.js:40656
(anonymous) # app.js:13896
__webpack_require__ # app.js:20
(anonymous) # app.js:13869
__webpack_require__ # app.js:20
(anonymous) # app.js:63
(anonymous) # app.js:66
app.js:36520 [Vue warn]: Invalid handler for event "click": got undefined
Why is this and how can I fix it?
To achieve what I wanted to do, I went the event-based way. For example, in my component, I added the method mounted, and added the following:
mounted()
{
eventHub.$on('signup-click', this.toggleModal)
}
And my main Vue method I added a simple event emitter method like so:
const app = new Vue({
el: '#app',
methods: {
generateEvent: (event) => {
eventHub.$emit(event);
}
}
});
And then in my blade file, I simple called that method like so:
<button class="bg-blue w-full rounded-full text-white py-2 mb-2" #click="generateEvent('signup-click')">
{{ __("Sign up") }}
</button>
Which simple emits the event, so my component listens to it, then calls that child method.

Resources