Can't get component data initialization with prop value to work - vue.js

I'm trying to pass an array as prop to a child component to use it there localy.The main idea would be to be able to edit this array in child component and then pass it to php via axios.
When i try to initialize the childs local data with the prop i get an empty array as data.
parent component
<template>
<stocare-comanda v-if="isStocareComandaActive == true" :comanda="comanda" :id="id"></stocare-comanda>
</template>
<script>
export default {
data: function() {
return {
lista: [],
comanda: [],
id: "",
isStocareComandaActive: "false"
};
},
methods:{
stocare: function() {
this.id = event.currentTarget.id;
this.isStocareComandaActive = true;
axios
.post("../stocare/deschide_comanda", { id: this.id })
.then(response => {
this.comanda = response.data.data;
// console.log(response.data);
});
}
}
};
</script>
child component
<script>
export default {
props: ["id", "comanda"],
data: function() {
return {
cmd: this.comanda
};
},
methods: {},
mounted() {
}
};
</script>
Expected result:
In my child component, cmd should get the array from comanda prop.
Actual result:
actual result

this is merely speculation but I am guessing during the initialization of your child component, this.comanda in your parent component is empty, and thus it initializes cmd with empty array.
try setting
mounted(){
this.cmd = this.comanda
}
if this doesn't work it means you are changing comanda variable in your parent so
return{
cmd: this.comanda
}
doesn't work, because this only sets cmd in the first initialization of the variable.

Related

Computed properties are not reacting to state change

I am working on a product overview page, that send out an API-call based on the current Category you are looking at:
store.dispatch("tweakwise/fetchAPIAttributesLayeredNavigation", {
tweakwiseCategory,
this.pageNumber,
}
In my Store, the data from this API-call will be set in the following VueX Store State:
this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes: []
I want to react to this data in my front-end but my Computed methods do not seem to react to this change. As you can also see in the function below I added a Catch to prevent a "Non defined" error. The function, however, will not be called after the state has been set.
This computed property is also added to the Mount() op the component
computed: {
initialFetchProducts() {
this.fetchProducts(
this.$store.state.tweakwise?.tweakwiseLayeredNavigationAttributes || []
);
},
},
make computed property for state you want to watch,
than create watch() for this prop. In watch you can react on computed property change.
<template>
<div v-for="product in products"></div>
</template>
<script>
export default {
data: {
return {
products: [],
}
},
computed: {
tweakwiseLayeredNavigationAttributes() {
return this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes;
},
},
watch: {
// on every tweakwiseLayeredNavigationAttributes change we call fetchProducts
tweakwiseLayeredNavigationAttributes: {
handler(newValue, oldValue) {
this.fetchProducts(newValue);
},
deep: true, // necessary for watching Arrays, Object
immediate: true, // will be fired like inside mounted()
}
},
methods: {
async fetchProducts(params) {
const products = await axios.get('/api', params);
this.products = products;
}
}
};
</script>

Is it possible to watch injected property

I am building an application which is using Vue 3 and I am providing a property in a parent component which I am subsequently injecting into multiple child components. Is there any way for a component which gets injected with this property to watch it for changes?
The parent component looks something like:
<template>
<child-component/>
<other-child-component #client-update="update_client" />
</template>
<script>
export default {
name: 'App',
data() {
return {
client: {}
}
},
methods: {
update_client(client) {
this.client = client
}
},
provide() {
return {
client: this.client
}
},
}
</script>
The child component looks like:
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
watch: {
client(new_client, old_client) {
console.log('new client: ', new_client);
}
}
}
</script>
I am trying to accomplish that when the provided variable gets updated in the parent the children components where its being injected should get notified. For some reason the client watch method is not getting called when client gets updated.
Is there a better way of accomplishing this?
Update
After further testing I see that there is a bigger issue here, in the child component even after the client has been updated in the parent, the client property remains the original empty object and does not get updated. Since the provided property is reactive all places it is injected should automatically be updated.
Update
When using the Object API reactive definition (data(){return{client:{}}), even though the variable is reactive within the component, the injected value will be static. This is because provide will set it to the value that it is initially set to. To have the reactivity work, you will need to wrap it in a computed
provide(){
return {client: computed(()=>this.client)}
}
docs:
https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity
You may also need to use deep for your watch
Example:
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
watch: {
client: {
handler: (new_client, old_client) => {
console.log('new client: ', new_client);
},
deep: true
}
}
}
</script>
As described in official documentation ( https://v2.vuejs.org/v2/api/#provide-inject ), by default, provide and inject bindings are not reactive. But if you pass down an observed object, properties on that object remain reactive.
For objects, Vue cannot detect property addition or deletion. So the problem in your code might be here:
data() {
return {
client: {}
}
},
Since you change the client property of this object ( this.client.client = client ), you should declare this key in data, like this:
data() {
return {
client: { client: null }
}
},
Now it becomes reactive.
I did a code sandbox reproducing your code watching an injected property: https://codesandbox.io/s/vue-inject-watch-ffh2b
For some reason the only way I got this to work was by only updating properties of the initial injected object instead of replacing the whole object. I also was not able to get watch working with the injected property despite setting deep: true.
Updated parent component:
<template>
<child-component/>
<other-child-component #client-update="update_client" />
</template>
<script>
export default {
name: 'App',
data() {
return {
client: {}
}
},
methods: {
update_client(client) {
this.client.client = client
}
},
provide() {
return {
client: this.client
}
},
}
</script>
Updated child component:
<template>
<button #click="get_client">Get client</button>
</template>
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
methods: {
get_client() {
console.log('updated client: ', client);
}
}
}
</script>
create a new value and reference the value from inject into it
inject: ['client'],
data: () => ({
value: null,
}),
created() {
this.value = this.client;
},
watch: {
value: {
handler() {
/* ... */
},
deep: true,
}
}
Now you can watch the value.
Note: "inject" must be an object
I ran into the same issue. But i just had to look more closely for details in the docs to make it work. In the end everything worked fine for me.
I built a vue plugin providing a Map together with some function as a readonly ref. Then it starts changing the Map contents once a second:
plugin.js
import { ref, readonly } from 'vue';
const rRuns = ref( new Map() );
let time = 0;
export default
{
install(app, defFile)
{
...
app.provide( "runs", readonly(
{ ref: rRuns,
get: (e) => rRuns.value.get( e ),
locationNames: () => rRuns.value.keys(),
size: () => rRuns.value.size,
} ) );
...
setInterval( () =>
{ time++;
const key = (time * 7) % 10;
console.log(" runs update", key, time);
rRuns.value.set( key.toString(), time )
}, 1000);
console.log(" time Interval start" );
}
}
main.js:
import { createApp } from 'vue'
import App from './App.vue'
import plugin from 'plugin.js';
const app = createApp(App);
app.config.unwrapInjectedRef = true;
app.use(game, 'gamedefs.json');
app.mount('#app');
runs.vue:
<template>
<h1>Runs:</h1>
<p v-if="!runs.size()">< no runs ></p>
<p v-else>runs: {{ runs.size() }}</p>
<button v-for="r of runs.locationNames()" :key="r" #click="display( r )">[{{ r }}]</button>
</template>
<script>
export default {
name: 'Runs',
inject:
{
runs: { from: 'runs' },
},
watch:
{
'runs.ref':
{
handler( v )
{
console.log("runs.ref watch", v );
},
immediate: true,
deep: true,
},
},
}
</script>

Vue watch not triggerring on elements in array

I have two components. One of them creates instances of the other one and adds them to an array then I try to watch on it to execute some functions, but it never triggers. My structure looks something like this:
const Child = Vue.extend({
data() {
triggerMe: false
},
methods: {
triggerSomething: function() {
console.log('It's called for sure');
this.triggerMe = true;
}
}
});
const Parent = Vue.extend({
data() {
children: []
},
components: {
child: Child
},
methods: {
addChild: function() {
this.children.push(new Child());
this.children[this.children.length - 1].$watch('triggerMe', _ => console.log('Never called'));
}
}
});
It works just right if it's the only one. How to fix that?
What I found is that $watch only triggers when component is mounted to some element in document. Other approach is needed:
<scipt id="parent-template">
<child
#trigger="triggerThat"
></child>
</script>
<script>
// in Child
this.$emit('trigger', someData);
// in Parent
methods: {
triggerThat: function(data) {
console.log('I can hear you');
console.log(data);
}
}
</script>

VueJs Data Passed From Root to Child Component via Prop Results in only an observable object

I have an app which calls a web service in the created() function and populates a property of the root data object. The property is passed via a prop to a child component and using the Chrome dev tools I can see that the prop data is available on the child component.
The problem I have is that I try to set data properties in the child component using values passed via the prop I end up with undefined property data. If I use the Chrome inspection tools and add a breakpoint I can see that the prop is an observable object in the form of {__ob__: Observer} and as such, I cannot directly access any of the data. My suspicion is that the child object sets it's data properties before the web service call has completed in the root.
How can I overcome this?
I've created a JsFiddle for this:
https://jsfiddle.net/ProNotion/a8c6nqsg/
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {
customerEmail: this.customer_data.customerEmail1
}
}
}
});
new Vue({
el: "#app",
data() {
return {
customer: {}
};
},
methods: {
init() {
var self = this;
axios.get("https://0bb1313e-b089-432e-b6bc-250f6162d7f0.mock.pstmn.io/GetCustomerData")
.then(response => {
self.customer = response.data;
}).catch(response => {
console.error(response);
});
}
},
created() {
this.init();
}
});
Here is my HTML markup:
<div id="app">
<mycomponent :customer_data="customer" />
</div>
<script type="x-template" id="my-component-template">
<div>
<p>{{form_data.customerEmail1}}</p>
</div>
</script>
Check response data type and format
console.log(typeof response.data) // string
{ "customerEmail1": "me#example.com", } // Remove `,`
You must parse to JSON type
axios.get(...).then(response => {
self.customer = JSON.parse(response.data.replace(',', ''))
})
Set property to watch with `deep` option
[Deep watching](https://v2.vuejs.org/v2/api/#vm-watch) will be detect nested value changes inside Objects
```
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {}
}
},
watch: {
customer_data: {
handler (val) {
this.form_data = val;
},
deep: true
}
}
});
```
Demo: https://jsfiddle.net/ghlee/f4gewvqn

VueJS - Accessing store data inside mounted

I'm having trouble understanding the following:
I have a store which contains variables needed for the application. In particular, there is a globalCompanies which stores:
globalCompanies: {
current: [],
all: [],
currentName: "",
}
Inside another component, I want to do the following:
mounted() {
this.$store.dispatch( "fetchUsers" );
var currentName = this.$store.state.globalCompanies.currentName;
console.log(currentName);
},
However, this just shows as empty. I know the value is there because I have computed which returns the currentName and it works fine inside the view itself. It just doesn't like the fact that it's in the mounted component.
Where am I going wrong and what can I do to resolve this issue? I really need to capture the companies Name in order to use it for some real time events.
As a result of our discussion:
In the question Vuex state value, accessed in component's mounted hook, returns empty value, because it is set in an async action which does not resolve before mounted executes.
When you need to trigger some function when async action in Vuex resolves with a value, you can achieve it using watch on a computed property, which returns a value from your Vuex state. When a value in store changes, the computed property reflects these changes and watch listener executes:
const store = new Vuex.Store({
state: {
globalCompanies: {
test: null
}
},
mutations: {
setMe: (state, payload) => {
state.globalCompanies.test = payload
}
},
actions: {
pretendFetch: ({commit}) => {
setTimeout(() => {
commit('setMe', 'My text is here!')
}, 300)
}
}
})
new Vue({
el: '#app',
store,
computed: {
cp: function() { // computed property will be updated when async call resolves
return this.$store.state.globalCompanies.test;
}
},
watch: { // watch changes here
cp: function(newValue, oldValue) {
// apply your logic here, e.g. invoke your listener function
console.log('was: ', oldValue, ' now: ', newValue)
}
},
mounted() {
this.$store.dispatch('pretendFetch');
// console.log(this.cp, this.$store.state.globalCompanies.test); // null
// var cn = this.$store.state.globalCompanies.test; // null
// console.log(cn) // null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vuex#2.3.1"></script>
<div id="app">
{{ cp }}
</div>
VueJS - Accessing Store Data Inside Mounted
Ran into this issue and it turned out to be a scope issue.
Store:
export default () => {
items:[],
globalCompanies:{
current:[],
all:[],
currentName: "Something"
},
ok: "Here you go"
}
Getters:
export default {
getGlobalCompanies(state){
return state.globalCompanies;
}
}
Mounted: This works...
mounted() {
// Initialize inside mounted to ensure store is within scope
const { getters } = this.$store;
const thisWorks = () => {
const globalCompanies = getters.getGlobalCompanies;
}
},
This is Bad: Reaching for the store outside the mounted scope
mounted() {
function ThisDontWork() {
const { getters } = this.$store; // this.$store == undefined
}
ThisDontWork();
},