Passing props into component which has v-if set - vue.js

I have a page with a stepper. I conditionally display a step, based on what step user is on. When flicks between steps (user can go back and forth) I noticed that my props are no longer passed over. What can be done about it?
View page:
<v-container v-if="stepNumber === 1">
<sectionOne :restrictions="restrictions" />
</v-container>
<v-container v-if="stepNumber === 2">
<sectionTwo :restrictions="restrictions" />
</v-container>
Within a section component:
#Prop() readonly restrictions: RestrictionsDomain = new RestrictionsDomain([]);
I understand that I can avoid a need for passing a prop with help of vuex, but I am wondering if anything can be done to get my props to work as is? Also, I can't use v-show for this either.

Since v-if destroys the element, thus removing the props' reactivity, and if you can't use v-show, I'd consider using a watcher on the prop and setting another reactive variable that'd trigger the visibility of that element.
So you'd get something like this:
<v-container v-if="step === 1">
<sectionOne :restrictions="restrictions" />
</v-container>
<v-container v-if="step === 2">
<sectionTwo :restrictions="restrictions" />
</v-container>
props: ['stepNumber'],
data() {
return {
step: 1,
}
},
watch: {
stepNumber(newVal) {
this.step = newVal
}
}

Related

Components rendering before Vuex state updates

I'm having trouble with vuex getters; On the first log in, with vuex in its initial state, actions which set some state properties are dispatched, however, subsequent usage of getters still retrieve null (the initial value for state properties)
I have the following in my vue component's script:
beforeCreate() {
store.dispatch('getSomething', 1).then(() => {
this.loading = false
})
},
computed: {
...mapGetters({
something: 'getSomething'
})
}
in the template:
<v-row v-if="!loading">
...
<span class="text-16">{{ something.name }}</span>
...
</v-row>
In the Something store:
const getters = {
getSomething: state => new Something(
state.something.id,
state.something.name,
state.something.description,
)
}
My expectation is that the action would be called before the component is loaded and being synchronous, the state should be filled by the said action which commits a mutation that sets the state.
Instead I get the following error which points to the getter:
TypeError: Cannot read properties of null (reading 'id')
at getSomething(something.js?62ce:12:1)
Update (mwe)
<template>
<v-row v-if="!loading">
<v-col class="mt-3" cols="12" lg="3">
<base-card>
<v-row>
<v-col class="text-center" cols="12">
<v-avatar>
<v-icon>mdi-liquid-spot</v-icon>
</v-avatar>
<div class="card-title ma-1 text-h5">{{ something.name }}</div>
<div class="d-flex align-center justify-center">
<span class="text-16">{{ something.description}}</span>
</div>
</v-col>
</v-row>
</base-card>
</v-col>
</v-row>
</template>
<script>
import store from "#/store";
import {mapGetters} from "vuex";
export default {
name: "Dashboard",
beforeCreate() {
// getSomething action
store.dispatch('getSomething', 2).then(() => {
this.loading = false
})
},
computed: {
...mapGetters({
// getSomething getter
something: 'getSomething'
})
},
methods: {},
data() {
return {
loading: true
}
}
}
</script>
<style scoped>
</style>
Your problem has (most likely) nothing to do with vuex.
This is probably a case of replacing a component with another component of the same type. When that happens, unless the component is key-ed using a unique primitive identifier, the component instance is reused (by Vue), so beforeCreate is only called once.
After that, whenever the component is updated, the beforeCreate hook is no longer called, (only beforeUpdate and updated hooks are called).
You either key the child component using a unique primitive identifier (perhaps something.id!?). Or you use beforeUpdated hook.
Another important aspect is the store action is asynchronous. Do not expect the creation of the component or its mounting to be waiting for the action to resolve. If that's what you want, you should call the action from parent component and condition the rendering of the child component (using v-if) on something that gets set when the action has resolved (and which gets unset when you dispatch the action again, to get another "something").
If my answer doesn't help you, consider creating a runnable Minimal, Reproducible Example. What you posted so far is not enough to create one and test potential solutions.
This makes your question unanswerable, renders it useless for future users having a similar problem and will likely result in the question being closed as off-topic.

VueJS can't update v-text-field value dynamically

I dynamically draw form input component (as in the image) using this code:
In this case the key can be "name","gruppo","codice" and so on.
<v-row>
<v-col v-for="(key,i) in keys_visible" :key="key" v-if="headers_visible[i].visible == true" cols="12" sm="12" md="12"
v-if="!(headers_visible[i].type == 'bit' && editedItem[key] == -9)">
<v-text-field #change="comp_change(key)" v-else-if="headers_visible[i].type == 'varchar'" v-model="editedItem[key]" :label="headers_visible[i].text"></v-text-field>
</v-col>
</v-row>
Then I have comp_change function which is defined in methods block:
comp_change (par1) {
var self = this;
self.editedItem["name"] = "example text";
},
I have placed a debugger; at the beginning of comp_change function, and it stops everytime so the function is triggered, but without displaying new value in "Nome" field (which v-model is editedItem["name"]). Why after comp_change I can't see "example text" in the field?
The form is already opened when I fire change
This is likely a reactivity issue. You should read up on this here. Also, if you use v-model, you do not need to set the value yourself, meaning you can do away with the #change call. You have two options as I see it.
a. Use root data objects on your component instead of an array/object and then use v-model as normal. This looks like:
<template>
<v-text-field v-model="name" />
<v-text-field v-model="email" />
</template>
<script>
export default {
data() {
return {
name: '',
email: '',
etc: ''
}
}
}
</script>
Now, when your form fields are updated by the user, you won't need to use #change to set the value. It will happen automatically.
b. Or, set the model with Vue.set(). In this case, you are not going to use v-model. Instead, you have defined your own methods to manage the data. This looks like:
<template>
<v-text-field #change="comp_change(key)" />
</template>
<script>
import Vue from 'vue';
export default {
data() {
return {
editedItem: {}
}
},
methods: {
comp_change (par1) {
Vue.set( this.editedItem, 'name', 'example text' );
}
}
}
</script>

Vue js - How to use props in data and methods

I am new in vue js , I am passing data from parent component to child one using props and I can use it in child normally but I can't use it in data of child component
parent
<template>
<div>
<show-question :qdata="question" v-if="question"></show-question>
<h1 v-if="!question"> some error </h1>
</div>
</template>
<script>
import ShowQuestion from './ShowQuestion';
export default {
created(){
axios.get(`/api/question/${this.$route.params.slug}`)
.then(res => {
this.question = res.data.data
})
},
data(){
return {
question : {},
}
},
components:{
ShowQuestion,
},
}
</script>
child
<template>
<v-container>
<v-card>
<div>
<v-card-title class="blue--text"
>{{ data.title }}
<v-spacer></v-spacer>
<v-btn color="teal white--text">5 Replies</v-btn>
</v-card-title>
<v-card-subtitle
> {{data.uid}} {{ data.user }} said {{ data.created_at }}</v-card-subtitle
>
</div>
<v-card-text>{{ data.body }}</v-card-text>
<v-card-actions v-if="own">
<v-btn icon text>
<v-icon color="orange">create</v-icon>
</v-btn>
<v-btn icon text>
<v-icon color="red">delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
<script>
export default {
props: ['qdata'],
data(){
return {
own: User.own(this.qdata.uid),
};
},
};
</script>
this.qdata.uid is always be undefined in console, although it supposed it have values and I can saw it from child template
enter image description here
Your show-questioncomponent is mounted early on because v-if="question" is true. When show-question is mounted, your api call hasn't had a chance to finish, so question is the same as the initial value {}, which is why uid is undefined.
I would change question : {} to question: null, then the child component will only be mounted when there's a question (after the api call).
This is simply because if you check the truthy of an object it will always return true even if the object is empty, which results in the component being rendered before the API call has finished.
Instead, you can check if it's empty or not by converting it to an array and check it's length value, i.e. Object.entries(question).length or simply use the lodash helper method: _isEmpty(question).
Also a quick side note: it's cleaner to use v-else after v-if when you want to render something or the other instead of explicitly negating the value in another v-if, though they're required to be direct siblings.

Access data from another Component

now i have 2 Components
1 - is just a drop down list v-select
<v-row align="center" >
<v-col class="d-flex" cols="12" sm="6" v-if="Compounds" >
<v-select :items="Compounds"
v-model="selectedItems"
label="Select"
item-value="id"
item-text="name"
v-on:change="selectedCompound">
</v-select>
{{ selectedItems }}
</v-col>
</v-row>
with method
methods: {
selectedCompound(h2o) {
console.log(h2o);
console.log("This is from Selected Compound");
},
and i call it in another page
<div>
<SelectCompound></SelectCompound>
</div>
now i want to get the method "selectedCompound" and recall it on this page
so i can access the ID of it to reload the page when the user select another name from the v-select
Props are passed down, Events are emited up. If you want to communicate directly between the parent and child, you pass props from parent to child, and the child reacts to the change in value. If you however want the parent to react to changes the child component, you need to emit events.
Here is an example.
Child
methods: {
selectedCompound(h2o) {
this.$emit('valChange', h2o)
},
}
Parent
<div>
<SelectCompound #valChange="handleChange"></SelectCompound>
</div>
methods: {
handleChange(h2o) {
// handle here
console.log('parent noticed change ' + h2o)
},
}
You can also use a bus (like Vuex) to have all components communicate to a separate state manager, but it increases the complexity quite a bit compared to simple even emit.
I made this jsfiddle for you, using the localStorage as persistence if u need to reload the page, and emitting a event when any option of the select is selected, this event triggered is called change on the select tag, then just you have emit to the parent the value selected.
And using the life cycle method created() of Vue to init the value from the persistence.

How to use another method's variable in a vue component?

I have two methods in a vue component.
First makes the user choose from a v-select, either itemone or itemtwo. Then, to retreive the value for later i call #change to assign the variable to a method declared later - getItemValue.
Second is a submit button, when clicked, we go to handleSubmit.
After handleSubmit is called, I want to use the value I got from getItemValue (in variable theItem), but how can I call another method if it's out of my scope?
Mycomponent.vue
<template>
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-select
v-model="select"
:items="items"
#change="getItemValue"
></v-select>
<v-btn
#click="handleSubmit"
>
Submit
</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
items: [
'itemone',
'itemtwo'
],
}),
methods: {
getItemValue(theItem) {
},
handleSubmit(e) {
e.preventDefault()
// i need "theItem" here!
}
},
}
</script>
v-model already writes to your local variable, so there is absolutely no need to setup a get method to write the select value to a variable.
Actually, v-model is a bit more complicated than just 'write' to a variable, but the important bit is that in your template you are setting up v-model="select", which basically means that whenever the user uses the select to pick a value, your local select variable will be updated with the selected value.
Now, there is no select in your example component data, I don't know why. But if you had it, you could just sent that variable in your handleSubmit:
<template>
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-select
v-model="select"
:items="items"
></v-select>
<v-btn
#click="handleSubmit"
>
Submit
</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
select: '',
items: [
'itemone',
'itemtwo'
],
}),
methods: {
handleSubmit(e) {
e.preventDefault()
doSomethingWith(this.select); // this will be updated at this point
// with the option the user selected
}
},
}
</script>
Now, however, be aware that if the select variable is a component prop, then you should not do this right away, since props are not intended to be modified directly by child components. If that would be the case, please update your question with more info.
You would simple set the variable (theItem) value to the data
getItemValue(theItem) {
this.theItem;
},
and then retrieve it later
handleSubmit(e) {
e.preventDefault()
// i need "theItem" here!
// simple access theItem
console.log('theItem', this.theItem);
}