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

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);
}

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>

Make Vuetify v-text-field component required by default

Currently, you can set rules that will work once the user changes the value of the input. For example:
Template part
<v-text-field
v-model="title"
label="Title"
></v-text-field>
Logic
export default {
data () {
return {
title: '',
email: '',
rules: {
required: value => !!value || 'Required.'
},
}
}
}
When the user focuses and removes focus from that element, or when the user deletes all its content, the required rule is triggered.
But what happens if we want to start with the rule enabled as soon as the component is mounted or created? Is there a way to achieve this?
I searched around vuetify but I could not find info about this nor examples in my humble google searches. I will appreciate help. I'm new in vue world. Thanks.
You could do the following:
Create a v-form and place your textfields inside the form. Don't forget to give your v-form a v-model and a ref too.
On mounted you can access the v-form via this.$refs and call .validate() just as Jesper described in his answer. In the codesandbox below you can see that the textfields immediately go red and display the "Required." text.
<v-form v-model="formValid" ref="myForm">
<v-text-field label="Field 1" :rules="rules.required"></v-text-field>
<v-text-field label="Field 2" :rules="rules.required"></v-text-field>
</v-form>
<script>
export default {
data() {
return {
formValid: false,
rules: {
required: [value => !!value || "Required."]
}
};
},
mounted() {
this.$refs.myForm.validate();
}
};
</script>
Example:
You should change your validation a little bit to achieve this.
<ValidationProvider rules="required" v-slot="{ errors }" ref="title">
<v-text-field
v-model="title"
label="Title"
></v-text-field>
</ValidationProvider>
And then you should call this.$refs.title.validate()
If you trigger this when mounted() is called, it should validate all the fields right away, as you're requesting.

How to prevent parent component from reloading when changing a parameterised child component in Vue js

I have a page where a ClientPortfolio (parent component) containing a list of Securities (child component) are loaded in a v-data-table list.
The issue I have is that ClientPortfolio is fully reloaded every time I click on a security in the list causing the entire list to be refreshed causing scroll and selected class to reset, as well as unncessary performance overhead.
I have looked at the documentation of Vue and nothing seems to point out how to only refresh a child component when it has parameters, it looks like the parent component is being refreshed as the route is changing every time a security is selected, despite expecting that Vue would know that only sub (nested route) is changing hence need to only reload the child component
The closest answer I got was explained on https://github.com/vuejs/vue-router/issues/230 which does not explain in the code how to achieve this.
routes.js:
routes: [
{
path: '/client/:clientno/portfolios/:portfolioNo',
component: ClientPortfolios,
children: [
{ path: 'security/:securityNo', component: Security }
]
},
]
Router link in ClientPortfolios.vue:
<router-link tag="tr" style="cursor:pointer"
:to="`/client/${$route.params.clientno}/portfolios/${selectedPortfolioSequenceNo}/security/${props.item.SecurityNo}-${props.item.SequenceNo}`"
:key="props.item.SecurityNo+props.item.SequenceNo">
</router-link>
Router view (for Security component) in ClientPortfolios.vue:
<v-flex xs10 ml-2>
<v-layout>
<router-view :key="$route.fullPath"></router-view>
</v-layout>
</v-flex>
Any hint on how to prevent parent from getting reloaded is appreciated.
EDIT: Trying to get closer to the issue, I notice that the "Key" attr in ClientPortfolios changes (as shown in the Vue debug window above) whenever I change the Security, could that be the reason? Is there a way to assign a key to ClientPortfolios component although its not a child one? Or a way to not update its key when navigating to different securities?
UPDATE: Full code
ClientPortfolios.vue
<template>
<v-layout row fill-height>
<v-flex xs2>
<v-layout column class="ma-0 pa-0 elevation-1">
<v-flex>
<v-select v-model="selectedPortfolioSequenceNo" :items="clientPortfolios" box label="Portfolio"
item-text="SequenceNo" item-value="SequenceNo" v-on:change="changePortfolio">
</v-select>
</v-flex>
<v-data-table disable-initial-sort :items="securities" item-key="Id" hide-headers hide-actions
style="overflow-y: auto;display:block;height: calc(100vh - 135px);">
<template slot="items" slot-scope="props">
<router-link tag="tr" style="cursor:pointer"
:to="{ name: 'Security', params: { securityNo: props.item.SecurityNo+'-'+props.item.SequenceNo } }"
>
</router-link>
</template>
<template v-slot:no-data>
<v-flex class="text-xs-center">
No securities found
</v-flex>
</template>
</v-data-table>
</v-layout>
</v-flex>
<v-flex xs10 ml-2>
<v-layout>
<keep-alive>
<router-view></router-view>
</keep-alive>
</v-layout>
</v-flex>
</v-layout>
</template>
<script>
import Security from '#/components/Security'
export default {
components: {
security: Security
},
data () {
return {
portfoliosLoading: false,
selectedPortfolioSequenceNo: this.$route.params.portfolioNo,
selectedPortfolio: null,
securityNo: this.$route.params.securityNo
}
},
computed: {
clientPortfolios () {
return this.$store.state.ClientPortfolios
},
securities () {
if (this.clientPortfolios == null || this.clientPortfolios.length < 1) {
return []
}
let self = this
this.selectedPortfolio = global.jQuery.grep(this.clientPortfolios, function (portfolio, i) {
return portfolio.SequenceNo === self.selectedPortfolioSequenceNo
})[0]
return this.selectedPortfolio.Securities
}
},
mounted () {
this.getClientPortfolios()
},
activated () {
},
methods: {
changePortfolio () {
this.$router.push({
path: '/client/' + this.$route.params.clientno + '/portfolios/' + this.selectedPortfolioSequenceNo
})
},
getClientPortfolios: function () {
this.portfoliosLoading = true
let self = this
this.$store.dispatch('getClientPortfolios', {
clientNo: this.$route.params.clientno
}).then(function (serverResponse) {
self.portfoliosLoading = false
})
}
}
}
</script>
Security.vue
<template>
<v-flex>
<v-layout class="screen-header">
<v-flex class="screen-title">Security Details </v-flex>
</v-layout>
<v-divider></v-divider>
<v-layout align-center justify-space-between row class="contents-placeholder" mb-3 pa-2>
<v-layout column>
<v-flex class="form-group" id="security-portfolio-selector">
<label class="screen-label">Sequence</label>
<span class="screen-value">{{security.SequenceNo}}</span>
</v-flex>
<v-flex class="form-group">
<label class="screen-label">Security</label>
<span class="screen-value">{{security.SecurityNo}}-{{security.SequenceNo}}</span>
</v-flex>
<v-flex class="form-group">
<label class="screen-label">Status</label>
<span class="screen-value-code" v-if="security.Status !== ''">{{security.Status}}</span>
</v-flex>
</v-layout>
</v-layout>
</v-flex>
</template>
<script>
export default {
props: ['securityNo'],
data () {
return {
clientNo: this.$route.params.clientno,
securityDetailsLoading: false
}
},
computed: {
security () {
return this.$store.state.SecurityDetails
}
},
created () {
if (this.securityNo.length > 1) {
this.getSecurityDetails()
}
},
methods: {
getSecurityDetails: function () {
let self = this
this.securityDetailsLoading = true
this.$store.dispatch('getSecurityDetails', {
securityNo: this.securityNo,
clientNo: this.clientNo
}).then(function (serverResponse) {
self.securityDetailsLoading = false
})
}
}
}
</script>
router.js
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
component: Dashboard
},
{
path: '/client/:clientno/details',
component: Client,
props: true
},
{
path: '/client/:clientno/portfolios/:portfolioNo',
component: ClientPortfolios,
name: 'ClientPortfolios',
children: [
{ path: 'security/:securityNo',
component: Security,
name: 'Security'
}
]
}
]
})
UPDATE:
Just to update this as it’s been a while, I finally got to find out what the problem is, which is what #matpie indicated elsewhere, I have found out that my App.vue is the culprit where there is a :key add to the very root of the application: <router-view :key="$route.fullPath" /> this was a template I used from somewhere but never had to look at as it was "working", after removing the key, all is working as it should, marking matpie answer accepted.
Preventing component reload is the default behavior in Vue.js. Vue's reactivity system automatically maps property dependencies and only performs the minimal amount of work to ensure the DOM is current.
By using a :key attribute anywhere, you are telling Vue.js that this element or component should only match when the keys match. If the keys don't match, the old one will be destroyed and a new one created.
It looks like you're also pulling in route parameters on the data object (Security.vue). Those will not update when the route parameters change, you should pull them in to a computed property so that they will always stay up-to-date.
export default {
computed: {
clientNo: (vm) => vm.$route.params.clientno,
}
}
That will ensure that clientNo always matches what is found in the router, regardless of whether Vue decides to re-use this component instance. If you need to perform other side-effects when clientNo changes, you can add a watcher:
vm.$watch("clientNo", (clientNo) => { /* ... */ })
Could you please check again after removing the local registration of the security component? As it's not needed because this is being handled by the vue router itself.
components: { // delete this code
security: Security
},
Instead of using router here. Declare two variable at root level for selected security and portfolio,
list the securities based on the selected portfolio.
on selecting a security from displayed securities, update the root variable using,
this.$root.selectedSecurityId = id;
you can have watch at security component level.
In root,
<security selectedid="selectedSecurityId" />
In component security,
....
watch:{
selectedid:function(){
//fetch info and show
}
}
...
the components will be look like following,
<portfolio>
//active. list goes here
</portfolio>
........
<security selectedid="selectedSecurityId">
//info goes here
</security>
Above approach will help to avoid routers. hope this will help.
I had a similar issue once. IMO it was caused by path string parsing.
Try to set a name for your route. And replace your router-link to param with an object.
And remove router-view :key prop. It doesn't need to be there. It is used to force component update when a route changes. It is usually a sign of bad code. Your component (Security) should react to route params update. Not the parent component force it to.
So, try to change your code to:
routes: [
{
path: '/client/:clientno/portfolios/:portfolioNo',
component: ClientPortfolios,
name: "ClientPortfoliosName", // it can be anything you want. It`s just an alias for internal use.
children: [
{
path: 'security/:securityNo',
name: "PortfolioSecurities", // anyway, consider setting route names as good practice
component: Security
}
]
},
]
<router-link tag="tr" style="cursor:pointer"
:to="{ name: 'PortfolioSecurities', params: { clientno: $route.params.clientno, portfolioNo: selectedPortfolioSequenceNo, securityNo: props.item.SecurityNo+'-'+props.item.SequenceNo } }"
:key="props.item.SecurityNo+props.item.SequenceNo">
</router-link>
And it should work.
P.S. In your router-link you shall point to the route you want to navigate to. In this case PortfolioSecurities

Why is the this.$on() callback triggered upon mount of a component?

I have a component which $emit to its parent upon some activity.
The parent listens to this event and triggers a function upon reception. This is assembled in mount():
<template>
<v-app>
<v-toolbar fixed app>
<v-toolbar-title v-text="title"></v-toolbar-title>
<v-spacer></v-spacer>
<v-btn color="error" dark large>Large Button</v-btn>
</v-toolbar>
<v-content>
<new-case v-on:dirty="updateDirty"></new-case>
</v-content>
<v-footer app>
<span>© 2017</span> dirty: {{dirty}}
</v-footer>
</v-app>
</template>
<script>
export default {
data() {
return {
case: {
id: null,
title: null,
},
cases: [],
title: 'Vuetify.js',
dirty: false,
}
},
watch: {
dirty: () => {
console.log('requesting relaod of data')
}
},
methods: {
updateDirty(what) {
console.log('requesting update of dirty state '+what)
this.dirty = what
}
},
mounted() {
this.$on('dirty', this.updateDirty())
}
}
</script>
The whole mechanism works fine (the emmited even is correctly handled by the parent), except that when the component is mounted, I see in the console
17:36:01.380 App.vue?ea99:36 requesting update of dirty state undefined
17:36:01.449 App.vue?ea99:31 requesting relaod of data
17:36:01.561 backend.js:1 vue-devtools Detected Vue v2.5.13
Why is this.updateDirty() triggered upon the mount of the component? (even though nothing was emitted yet - not only the component which would emit something is not used yet, but the DevTools Vue panel does not show any events yet)
The issue is with you $on call itself. The parenthesis after updateDirty, this.$on('dirty', this.updateDirty()), is the culprit, it is telling JS to run the function and store the result as the event handler. Try this.$on('dirty', this.updateDirty) instead so you're passing the reference to the function not the result.