How to avoid warning "mutating a prop directly" using Vue and Laravel - laravel

I am using Laravel 7 and Vue.js 2.
I want to pass an eloquent query result from a controller to a Vue.js component (passing through a View and another component).
This is my controller:
$permissions = Permission::select('id')->get();
return view('admin')->with(['permissions'=>$permissions]);
This is my view:
<div id="app">
<admin-panel :permissions="{{ $permissions }}"></admin-panel>
</div>
This is admin-panel component (passing data through props):
<template>
<div>
<admin-operations></admin-operations>
<hr>
<insert-employee :permissions="permissions"></insert-employee></div>
</template>
This is insert-employee component script:
<script>
export default {
components: {
},
props: ['permissions'],
mounted() {
console.log('Component mounted.');
},
computed:{
},
data: function() {
return {
}
},
methods: {
}
}
</script>
This is the select in insert-employee component:
<select required v-model="permissions" class="form-control" id="permissions">
<option v-for="permission in permissions" :value="permission.id" :key="permission.id">
{{ permission.id }}
</option>
</select>
The results of the query should be visualized in the select options.
However in the select I can visualize correctly the values in the options, but when I select an option the selection doesn't work and in the console appears two times the following warning:
app.js:40160 [Vue warn]: 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. Prop being mutated: "permissions"
found in
---> <InsertEmployee> at resources/js/components/InsertEmployee.vue
<AdminPanel> at resources/js/components/AdminPanel.vue
<Root>
Can help?

Let's try this. I think we're just mixing the values up a bit. I'll try to think of props that are passed in as a read only thing. In this case permissions is an array that holds the id's of each permission.
Next we'll want to use that array to give our user object some values. This way we aren't trying to manipulate values of permissions. That's where Vue was initially getting angry.
<script>
export default {
components: {},
props: {
permissions: {
type: Array,
required: true,
default: () => [],
}
},
mounted() {
console.log('Component mounted.');
},
computed:{},
data: () => ({
user: {
permissionId: "",
}
}),
methods: {},
}
</script>
Then in your component template:
<select required v-model="user.permissionId" class="form-control" id="user-permissionId">
<option v-for="permission in permissions" :value="permission.id" :key="permission.id">
{{ permission.id }}
</option>
</select>

Related

Vuejs Sibling Component data passing with method

I have a Vuejs Laravel page with Parent and 2 child
In my Parent template I have
<template>
<div>
<h4>DATA ANALYSIS</h4>
<dataAnalysisGraph />
<br>
<hr>
<dataAnalysisTable />
</div>
and in my dataAnalysisGraph child component i have a method that send request everytime the dropdown changes.
//dataAnalysisGraph
<template>
<div>
<div>
<select class="form-control select" name="" #change="GenerateChart" v-model='chartType' >
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
<br><br><br>
<highcharts class="hc" :options="chartOptions" ref="chart"></highcharts>
</div>
</template>
<script>
export default {
data() {
return {
data: [],
}
},
methods: {
GenerateChart(){
this.axios
.get('api/'+this.chartType)
.then(response => {
this.data = response.data;
});
},
},
created() {
this.GenerateChart();
},
};
</script>
In my dataAnalysisTable child component. I want to get the this.data from dataAnalysisGraph component and pass it to the dataAnalysisTable and updates it every time the dropdown form dataAnalysisGraph component changes.
this is my dataAnalysisTable component currently
<template>
<div>
{{data}}
</div>
</template>
<script>
export default {
data() {
return {
data: [],
};
},
methods: {
},
created() {
},
};
</script>
You can emit an event inside dataAnalysisGraph returning this.data to the parent and connect this value using v-model to the dataAnalysisTable component. You can read more in the vuejs guide specifically in the "Usage with v-model" section.

Laravel vue.js and vuex link body text by id and show in a new component

I am very new to Laravel and Vuex, I have a simple array of post on my page.
test 1
test 2
test 3
I am trying to link the text on the AppPost.vue component and show the post that has been clicked on a new component (AppShowpost.vue) on the same page. I believe I have to get the post by id and change the state? any help would be good. Thank you.
when you click test 1 it will show "test 1" on a new component (AppShowpost.vue)
In side my store timeline.js, I belive I need to get the post by id and change the state ?
import axios from 'axios'
export default {
namespaced: true,
state: {
posts: []
},
getters: {
posts (state) {
return state.posts
}
},
mutations: {
PUSH_POSTS (state, data) {
state.posts.push(...data)
}
},
actions: {
async getPosts ({ commit }) {
let response = await axios.get('timeline')
commit('PUSH_POSTS', response.data.data)
}
}
}
My AppTimeline.vue component
<template>
<div>
<app-post
v-for="post in posts"
:key="post.id"
:post="post"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters({
posts: 'timeline/posts'
})
},
methods: {
...mapActions({
getPosts: 'timeline/getPosts'
})
},
mounted () {
this.getPosts()
}
}
</script>
My AppPost.vue component. I need to link the post.body to display the post in my AppShowpost.vue component.
<template>
<div class="w-full inline-block p-4">
<div class="flex w-full">
<p>
{{ post.body }}
</p>
</div>
</div>
</template>
<script>
export default {
props: {
post: {
required: true,
type: Object
}
}
}
</script>
My AppSowpost.vue component that needs to display the post that is clicked.
<template>
<div>
// Displaypost ?
</div>
</template>
<script>
export default {
// Get post from id ?
}
</script>
Okay you can create a new state in your vuex "current_state", asyou said, you can dispatch a mutation by passing the id to the vuex.
state: {
posts: [],
current_state_id : null
},
In your mutations
set_state_id (state, data) {
state.current_state_id = data;
}
On your app post.vue, you can set a computed property that watches the current state
computed: {
currentState() {
return this.$store.getters["timeline/current_state_id"];
}}
And create a watcher for the computed property to display the current id/post
watch: {
currentState: function(val) {
console.log(val);
},
Maybe this will help you. First I will recommend to use router-link. Read about router link here if your interested. It is very helpful and easy to use. But you will have to define the url and pass parameter on our vue-route(see bellow).
1.You can wrap your post.body in router-link as follow.
//With this approach, you don't need any function in methods
<router-link :to="'/posts/show/' + post.id">
{{ post.body }}
</router-link>
2. In your AppSowpost.vue component, you can find the post in vuex state based on url params as follow.
<template>
<div> {{ thisPost.body }} </div>
</template>
// ****************
computed: {
...mapState({ posts: state=>state.posts }),
// Let's get our single post with the help of url parameter passed on our url
thisPost() { return this.posts.find(p => p.id == this.$route.params.id) || {}; }
},
mounted() { this.$store.dispatch("getPosts");}
3. Let's define our vue route.
path: "posts/show/:id",
name: "showpost",
params: true, // Make sure the params is set to true
component: () => import("#/Components/AppShowPost.vue"),
Your Mutations should look as simple as this.
mutations: {
PUSH_POSTS (state, data) {
state.posts = data;
}
},
Please let me know how it goes.

How to transfer data between components placed in different views

I have two compoennts placed in one the same view.
#extends('layouts.app')
#section('content')
<bus></bus>
<bus2></bus2>
#endsection
I want to pass data (name) from one component to other one after clicking button. To do that I' using $emit function.
/// bus component
<template>
<div>
<p> Name Bus 1{{name}}</p>
<input type="button" #click="setName()" value="s"/>
</div>
</template>
<script>
export default {
created() {},
data: function() {
return {
name: "Volvo"
};
},
methods: {
setName: function(id) {
this.$root.$emit("sname", this.name);
}
},
props: []
};
</script>
///bus 2 component
<template>
<div>
<p> Name bus 2{{name}}</p>
</div>
</template>
<script>
export default {
created() {
this.$root.$on("sname", data => {
this.name = data;
});
},
data: function() {
return {
count: 0,
name: ""
};
},
methods: {}
};
</script>
Everything works fine. Name is transfered from bus to bus2. The problem exists when I place bus2 in different view - data are not transfered but code is the same. How can I transfer data between components placed in different views
Try using Vuex to specify your app state, and mutate this when it's necessary.
Vuex states are accessible from every components using this.$store or $store.

How to filter comments with vue.js?

In my view i have this :
<select class="sort_by">
<option selected disabled>SORT BY</option>
<option value="name" >Name</option>
<option value="date">Date</option>
</select>
In my controller i have this:
$comments = $article->comments()->orderBy('created_at','desc')->get();
In my vue i have this:
loadComments: function () {
articleid = this.article_id;
this.$http.get('/article/'+ articleid +'/allcomments').then(function(response){
// True
data = response.data.comments;
this.$set('all_comments', data);
this.comments= data;
}, function(response){
// False
});
},
What i want is when user select name or date, to change orderBy and then to display it in view without refresh. Any suggestion how can i do that?
EDIT:
In my ready function i have:
this.loadComments();
setInterval(function () {
this.loadComments();
}.bind(this), 20000);
So i cant sort by in vue.js, or can i?
You can use the Lodash sortBy method inside a computed property which acts as a filter. Lodash is included by default in the newest Laravel versions.
Your .vue file could look like this:
(NOTE: This is a Vue 2.0 example, if you are using a previous version of Vue some things can differ)
<template>
<select v-model="sortingBy" class="sort_by">
<option selected disabled>SORT BY</option>
<option value="name">Name</option>
<option value="date">Date</option>
</select>
<div v-for="comment in sortedComments">
// Here your HTML for each comment
<div>
</template>
<script>
export default {
data() {
return{
comments: {},
sortingBy: "name", // Default sorting type
}
},
computed: {
sortedComments: function () {
return _.sortBy(this.comments, [this.sortingBy]);
}
},
mounted() {
this.loadComments();
setInterval(function () {
this.loadComments();
}.bind(this), 20000);
},
methods: {
loadComments: function () {
this.$http.get('/article/' + this.article_id + '/allcomments').then((response) => {
this.comments = response.data;
});
},
}
}
</script>

Laravel vue-multiselect error

I am trying to implement vue-multiselect (version 1.1.3) with Laravel 5.
In my vue file I have this code:
<template>
<div class="dropdown">
<multiselect
:seleted="multiValue"
:show-labels="false"
:options="options"
:placeholder="placeholder"
:searchable="true"
:allow-empty="false"
:multiple="true"
key="name"
label="name"
#update="updateSelected"
></multiselect>
<label v-show="showLabel" for="multiselect"><span></span>Language</label>
</div>
</template>
<script>
import { Multiselect } from 'vue-multiselect';
export default {
components: { Multiselect },
props: {
selected: null,
options: {
type: Array, default: function () {
return []
}
},
placeholder: 'Select...'
},
methods: {
updateSelected (newSelected) {
this.selected = newSelected
}
}
}
</script>
In my blade file:
<div class="form-group">
<drop-down
:options="{{ $members_list->toJson() }}"
></drop-down>
</div>
In my controller:
$members = DB::table('members')
->orderBy('member_first_name', 'asc')
->get();
$members_list = $members->map(
function($member) {
return [
"value" => $member->member_id,
"label" => $member->member_first_name. " ". $member->member_last_name
];
}
);
When I run the page I get a select list with all the members in it, but when I try to select one, it turns Red, it is added to the selected list on top but I cannot select more options and in firebug I get this error:
[Vue warn]: You are setting a non-existent path "selected" on a vm instance. Consider pre-initializing the property with the "data" option for more reliable reactivity and better performance.
What am I missing?
Typo might be causing issues?
:seleted="multiValue"
Should be :selected="multiValue"
BTW :selected is deprecated in the version 2.0. :value has taken it's place.
I think it is because there is no variable called 'multiValue' in your component.
In :seleted="multiValue" , using the variable "options" instead of "multiValue"

Resources