Prop reactivity not working when passed from blade Laravel - laravel

I attempted to pass a prop from Blade file to Vuejs component name AppMessages
<app-messages :messages="{{ $messages }}"></app-messages>
Then messages are being rendered based on selfOwned boolean.
<app-message v-for="message in messages" :message="message" :key="message.id" v-if="message.selfOwned === false"></app-message>
<app-message-own v-for="message in messages" :message="message" :key="message.id" v-if="message.selfOwned === true"></app-message-own>
in the child component, I pass a message when created by a Bus event
let tempBuitMessage = this.tempMessage()
Bus.$emit('message.added', tempBuitMessage);
define this.messages in props
export default {
props: {
messages: {
required: true,
type: Array
}
},
in the parent component AppMessages I set up the listner
mounted() {
Bus.$on('message.added', data => {
this.messages.unshift(data)
console.log(this.messages)
});
},
Issue:
I expected the new passed message to but up in the chat but I can see it in the console (no error) but not rendered.

You cannot manipulate a component's props from within the component. Data flows down to children through props, and up to parents through events (not talking about a bus).
You should be able to get reactivity using a computed property, or just assigning messages on mount to a data property.
<template>
...
<app-message v-for="message in model" :message="message" :key="message.id" v-if="message.selfOwned === false"></app-message>
<app-message-own v-for="message in model" :message="message" :key="message.id" v-if="message.selfOwned === true"></app-message-own>
...
</template>
<script>
export default {
name: 'app-messages',
props: {
messages: {
required: true,
type: Array
}
},
data() {
return {
model: null,
};
},
created() {
this.model = this.messages;
},
mounted() {
Bus.$on('message.added', data => {
this.model.unshift(data)
console.log(this.messages)
});
},
}
</script>

Related

vue not loading data into child component

I've a hard time in understanding the methods of vue. In my put-request users can edit, delete images. In parent component the get-request loads the images and the are pushed to an image-gallery (the child-component) via properties. In my set up the console.log is always empty.
//PARENT COMPONENT
<template>
<div class="form-group">
<image-gallery :serverData="serverMap"/>
</div>
</template>
<script>
import ImageGallery from './ImageGallery.vue';
export default {
components:{ImageGallery},
data: () => ({
serverMap: {
title: '',
file: ''
}
}),
mounted () {
//AJAX ETC get servermap
.then((response) => {
this.serverMap = response.data
})
}
Just a normal straight parent-child situation. Here under the child-component
<template>
</template>
<script>
export default {
name: 'ImageGallery',
//incoming data
props: {
serverData: {
type: Object,
default () {
return {
hasLabels: true,
isHorizontal: false
}
}
}
},
created: function () {
this.loadImages()
},
methods: {
loadImages () {
console.log(this.serverData.file)
//do something with the serverData
//prepare for fileReader function
//together with new image validation
}
}
The method 'loadImages' should be automatically delevering the serverData via computed.But is doesn t. Who can help?
There is race condition.
Either not render a child until data is available; serverMap needs to be null instead of empty object in order to be distinguished from populated object:
<image-gallery v-if="serverMap" :serverData="serverMap"/>
Or delay data access in a child until it's available instead of doing this immediately in created:
watch: {
serverData(data) {
if (data)
this.loadImages()
}
}

Vuex get data via slim (Ruby on rails)

How to transfer data if I receive an array via Slim?
regions-list :region=#regions
regions-list - my component vue
:region - array with items
#regions - variable with items from backend
Im new with vuex, i think, i need something like this, but don’t know how to convey array with items
This is how you can organize the work of Vuex
export default new Vuex.Store({
state: {
reactions: [],
},
mutations: {
setReactions(state, segment) {
state.reactions = segment;
},
},
actions: {
async loadReactions({ commit }) {
try {
const reactions = '... response/request';
commit('setReactions', reactions); // Here we use mutation to put new data into state.
} catch (e) {
// ...
}
},
},
});
In your component vue regions-list
<template>
<div>{{ reactions }}</div> <!-- Here you can display and look at the content -->
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
name: 'RegionsList',
computed: {
...mapState(['reactions']), // This is get the state
},
created() {
this.loadReactions(); // Here you perform a function that receives data and puts it in state
},
methods: {
...mapActions(['loadReactions']),
},
};
</script>
<style scoped></style>

Maintaining button text state after page refresh using Vue.js and local storage

I am working on a task list using Vue.js component and Laravel, with a button to mark each individual task as "complete" or "incomplete". At the moment I can't even get it to change state, let alone maintain it after the page refresh. The console log says [Vue warn]: Error in mounted hook: "TypeError: Assignment to read-only properties is not allowed in strict mode".
CompleteButton.vue
<template>
<button type="button" #click="on_order_button_click()">
{{ buttonText }}
</button>
</div>
</template>
<script>
export default {
props: ['userId', 'item'], required: true,
data() {
return {
item2: this.item
}
},
methods: {
on_order_button_click() {
this.item2.is_complete = !this.item2.is_complete;
localStorage.setItem(this.item2.id, this.item2.is_complete);
}
},
mounted() {
var storedState = localStorage.getItem(this.item2.id);
if (storedState) {
this.item2.is_complete = storedState;
}
},
computed: {
buttonText() {
return this.item2.is_complete === true ? "Completed" : "Incomplete";
}
}
};
</script>
index.blade.php
<complete-button user-id="{{ $user->id }}" item="{{ $item}}"></complete-button>
You are assigning item2 as item prop and which is readonly since it's passed as a property so item2 keeping reference to the same readonly object.
You can simply use spread syntax or Object.assign method to create a new object.
item2: {...this.item}
UPDATE : As you commented, If it's a JSON string then simply parse it ans keep it as item2.
item2: JSON.stringify(this.item)

Vue.JS not update data into nested Component

I'm working with 3 VUE nested components (main, parent and child) and I'm getting trouble passing data.
The main component useget a simple API data based on input request: the result is used to get other info in other component.
For example first API return the regione "DE", the first component is populated then try to get the "recipes" from region "DE" but something goes wrong: The debug comments in console are in bad order and the variable used results empty in the second request (step3):
app.js:2878 Step_1: DE
app.js:3114 Step_3: 0
app.js:2890 Step_2: DE
This is the parent (included in main component) code:
parent template:
<template>
<div>
<recipes :region="region"/>
</div>
</template>
parent code:
data: function () {
return {
region: null,
}
},
beforeRouteEnter(to, from, next) {
getData(to.params.e_title, (err, data) => {
console.log("Step_1: "+data.region); // return Step_1: DE
// here I ned to update the region value to "DE"
next(vm => vm.setRegionData(err, data));
});
},
methods: {
setRegionData(err, data) {
if (err) {
this.error = err.toString();
} else {
console.log("Step_2: " + data.region); // return DE
this.region = data.region;
}
}
},
child template:
<template>
<div v-if="recipes" class="content">
<div class="row">
<recipe-comp v-for="(recipe, index) in recipes" :key="index" :title="recipe.title" :vote="recipe.votes">
</recipe-comp>
</div>
</div>
</template>
child code:
props: ['region'],
....
beforeMount () {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
},
The issue should be into parent beforeRouteEnter hook I think.
Important debug notes:
1) It looks like the child code works properly because if I replace the default value in parent data to 'IT' instead of null the child component returns the correct recipes from second API request. This confirms the default data is updated too late and not when it got results from first API request.
data: function () {
return {
region: 'IT',
}
},
2) If I use {{region}} in child template it shows the correct (and updated) data: 'DE'!
I need fresh eyes to fix it. Can you help me?
Instead of using the beforeMount hook inside of the child component, you should be able to accomplish this using the watch property. I believe this is happening because the beforeMount hook is fired before the parent is able to set that property.
More on the Vue lifecycle can be found here
More on the beforeMount lifecycle hook can be found here
In short, you can try changing this:
props: ['region'],
....
beforeMount () {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
},
To something like this:
props: ['region'],
....
watch: {
region() {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
}
},
Cheers!!

Vue JS: this.$on event doesn't contain passed parameters

Here are my components. I've successfully emitted parameters from the child component to the parent, but not from the parent to the child. The console.log('In initDetail()'); line fires, which means the $on event is triggered, but param is undefined. I'm wondering why.
Parent component:
components: {
"child-component": ChildComponent
}
methods: {
loadDetail() {
this.$emit('loadDetailEvent', 'test');
}
}
Child Component:
mounted() {
this.$on('loadDetailEvent', this.initDetail(param));
}
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
I've also tried calling a function right away. Neither the console.log('$on event'); nor the console.log(param); lines print.
this.$on('loadDetailEvent', function(param){
console.log('$on event');
console.log(param);
});
As pointed out in the comments, there are a couple misconceptions in your code:
Your parent component is emitting a loadDetailEvent event when the loadDetail method is called. Your child component is also listening for a loadDetailEvent event, but it will only handle this event if it is emitted by its own Vue instance. If the Vue instance of the parent emits an event, the child will have no knowledge of it.
You are attempting to handle the loadDetailEvent by firing initDetail and passing the parameters of the event to that method. But, in your code you are simply setting the loadDetailEvent handler to be the result of calling this.initDetail(param) within the scope of the mounted hook. What you would want to do in this case would be to specify an anonymous function as the handler, which receives the param value and passes it to this.initDetail:
this.$on('loadDetailEvent', (param) => this.initDetail(param))
But, to get at the root of your issue: it seems like you want to call a child method when a parent method is called. You could do this a few different ways:
As #Ohgodwhy suggested, you could create a separate, global Vue instance to use as an event bus.
You could set a ref (say ref="child") on the child component tag in your parent's template and then call the child component's method directly via this.$refs.child.initDetail('test').
As #B.Fleming suggested, you could set a watcher on the child component to react to a changing prop value passed by the parent component. This gets a little tricky since you would need to maintain the value of the variable being passed as the prop (I would use a .sync modifier, as you can see below).
Here are example snippets of the above solutions:
Using an event bus:
let EventBus = new Vue();
Vue.component('child', {
template: `<div>Child</div>`,
mounted() {
EventBus.$on('loadDetailEvent', (param) => this.initDetail(param));
},
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
});
new Vue({
el: '#app',
methods: {
loadDetail() {
EventBus.$emit('loadDetailEvent', 'test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child></child>
<button #click="loadDetail">load</button>
</div>
Using a ref:
Vue.component('child', {
template: `<div>Child</div>`,
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
});
new Vue({
el: '#app',
methods: {
loadDetail() {
this.$refs.child.initDetail('test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child ref="child"></child>
<button #click="loadDetail">load</button>
</div>
Using a prop value and a watcher:
Vue.component('child', {
template: `<div>Child</div>`,
props: { loading: String },
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
},
watch: {
loading(val) {
if (val) {
this.initDetail(val);
this.$emit('update:loading', '');
}
}
}
});
new Vue({
el: '#app',
data() {
return { payload: '' }
},
methods: {
loadDetail() {
this.payload = 'test';
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child :loading.sync="payload"></child>
<button #click="loadDetail">load</button>
</div>

Resources