I have two components called OrderComponent and ModalOrder (with vue-modal-js)
I passed the data from OrderComponent to ModalOrder, and in ModalOrder, I use an input tag to contain quantity_ordered and button to increment its value like this
<!-- ModalOrder.vue -->
<input v-model="order.quantity_ordered" />
<button #click.prevent="increment"></button>
in my script tag
// ModalOrder.vue
<script>
export default {
name: "ModalOrder",
methods: {
beforeOpen (event) {
// if there's a data passed from the OrderComponent, I put it to "order" data
this.order = event.params
// if there's no data passed a.k.a a new record, I have to set the default value to 0
if (this.order.quantity_ordered == undefined) {
this.order.quantity_ordered = 0
}
},
...
increment() {
this.order.quantity_ordered += 1
// this method will not increment the input UI, if it's a new record
},
},
data() {
return {
order : {
code: '',
table_name: '',
customer_name: '',
employee_name: '',
menu_name: '',
quantity_ordered: ''
},
}
}
}
</script>
My problem is whenever I want to make a new order data, then when I click the button to increment, the input value UI isn't incrementing
Thank you in advance.
You're falling prey to one of Vue's change detection caveats...
Vue cannot detect property addition or deletion
So for your new records, you would need to either set the property when you assign the new value to order
this.order = {
quantity_ordered: 0,
...event.params // if "quantity_ordered" is set here, it will override the default.
}
or dynamically set it after
if (this.order.quantity_ordered == undefined) {
this.$set(this.order, 'quantity_ordered', 0)
}
As mentioned in the comments, you should also default your data property to 0 if it's meant to be numeric
data: () => ({
code: '',
table_name: '',
customer_name: '',
employee_name: '',
menu_name: '',
quantity_ordered: 0 // 👈
})
Related
In my reducer I want to target a certain key but I think because it has an index, I can't target it with the methods in my reducer. I'm new so help would be appreciated.
Here is my code.
export const initialState = {
sheets: {
0: {
newTabsState: 'details',
name: "Sheet",
details: {
projectUnit: '',
projectName: '',
projectId: '',
projectCompany: '',
projectDesigner: '',
projectClient: ''
},
factors: {
memberName: '',
memberSpecies: '',
memberWeight: '',
memberLength: ''
},
forces: {
forcesUnit: '',
forcesName: '',
forcesId: '',
forcesCompany: '',
forcesDesigner: '',
forcesClient: ''
}
}
}
}
I want to be able to target newTabState so I can add a value to it but my IDE gives me an error when I try to add an index at the reducer method
The code below doesn't work... can you tell me what to do? I want to learn how.
const setNewtabState = (state, payload) => {
return {
...state,
sheets: {
...state,
newTabsState: payload
}
}
}
You can access newTabState like this initialState.sheets[0].newTabState.
I want to point out that when using an object in JavaScript as you do, there is no such thing as an index. An object only has keys and values and the key can be any arbitrary string or number. So what we access is a key represented by the number 0.
If you need proper indexes, you should use an array (which has the reduce method natively).
references:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
This should work:
return {
sheets: {
0: {
...state.sheets[0],
newTabsState: payload
}
}
So I have a data options like this
data() {
return {
userPayload: {
description: '',
languageCodes: [],
facebookUrl: '',
youtubeUrl: ''
},
}
},
Later I applied some functions to fill out each attributes in data. Then when I want to submit the data using handleSaveData(), I passed this userPayload object to axios and turns out it's only read the value of description attributes.
handleSaveData() {
...
const userPayload = {
description: this.userPayload.description,
profile: {
languageCodes: this.userPayload.languageCodes,
facebookUrl: this.userPayload.facebookUrl,
youtubeUrl: this.userPayload.youtubeUrl
}
}
console.log(this.userPayload)
// this one contains value of all attributes
console.log(userPayload)
// this one only shows value of description attributes, while the others are empty string or empty array
// i expect it also return value of another attributes
// because i already access it with this.userPayload.{attributeName}
}
I already tried out deepClone userPayload object but it doesn't work
Problem Statement:
I am trying to work on a design that involves replicating the Parties Being Served form fields on each Add button click. On clicking the Add Button, the corresponding PartyInfo object gets added to the PartyInfoList array
In the BaseButtonList.vue file, I use the PartyInfoList array to setup the v-for loop
<base-card
v-for="(data, index) of partiesInfoList" :key="index"
ref="childComponent"
#add-parties="updatePartiesInfoList"
#delete-party="deleteParty(index)"
:lastElement="index === partiesInfoListLength - 1"
>
However, it is important to note that initially the array has no elements. But, since I have no other way to enter the data to the array, I'm forced to load the form despite the partiesInfoList array having zero elements. I achieve this by setting the partiesInfoList with dummy data. This allows the initial form to be displayed. And when the Add Another button gets hit, this first real entry will be pushed to the 2nd index of the partiesInfoList.
However, this causes serious issues when deleting the entries from the array. The splice does function properly when I console log the output. But, Vue ends up deleting the wrong element from the DOM.
I have used a unique index key and also tried other possible keys, but all yield the same pernicious bug. Any help would be greatly appreciated. I think this is a really tricky design pattern, I can think of easier alternatives, but I want to get this working as a challenge. Maybe there's a better data flow I can set up or something.
Attached is the code
BaseCard.vue
<template>
//contains the template for the input form element
</template>
<script>
import { random } from '#amcharts/amcharts4/.internal/core/utils/String';
import { EventBus } from './bus.js';
export default {
emits:['add-parties','delete-party'],
props:['lastElement'],
data() {
return {
partyInfo:
{
id: '',
fullName: '',
preAuthorize: '',
serviceAddress: '',
},
validation: {
fullNameIsValid: true,
serviceAddressIsValid: true
},
hideAddButton: false,
formIsValid: true,
addServiceButtonText: '+ Add Service Notes (Optional)',
serviceNotes: [],
showServiceNotes: false,
showDeleteButton: true,
enteredServiceNote: '', //service notes addendum
}
},
computed : {
showServiceNotex(){
if(!this.showServiceNotes){
return '+Add Service Notes (Optional)'
}else{
return '- Remove Service Notes';
}
}
},
methods: {
setServiceNotes(){
this.showServiceNotes = !this.showServiceNotes;
},
addAnotherParty(){
this.validateForm();
if(this.counter === 0){
this.counter++;
this.lastElement = false;
}
if(!this.formIsValid){
return;
}
let emitObj = JSON.parse(JSON.stringify(this.partyInfo));
this.$emit('add-parties', emitObj); //event
},
deleteParty(){
this.$emit('delete-party');
},
validateForm(){
this.formIsValid = true;
if(this.partyInfo.fullName === ''){
this.validation.fullNameIsValid = false;
this.formIsValid = false;
}
if(this.partyInfo.serviceAddress === ''){
this.validation.serviceAddressIsValid = false;
this.formIsValid = false;
}
},
clearValidity(input){
this.validation[input] = true;
},
clearForm(){
this.partyInfo.fullName = '';
this.partyInfo.serviceAddress = '';
this.partyInfo.preAuthorize = false;
}
},
created(){
console.log('created');
}
}
</script>
Attached is the <BaseCardList> which renders the form elements in a v-for loop
BaseCardList.vue
<template>
<ul>
<base-card
v-for="(data, index) of partiesInfoList" :key="index"
ref="childComponent"
#add-parties="updatePartiesInfoList"
#delete-party="deleteParty(index)"
:lastElement="index === partiesInfoListLength - 1"
>
<!-- Wrapper for the `Parties Being Served` component-->
<template v-slot:title>
<slot></slot>
</template>
</base-card>
</ul>
</template>
<script>
import BaseCard from './BaseCard.vue';
export default {
components: { BaseCard },
data() {
return {
selectedComponent: 'base-card',
partiesInfoList : [
{id: 0,
fullName: 'dummy',
serviceAddress: 'dummy',
preAuthorize: ''
}
],
clearForm: false,
counter: 1
}
},
computed : {
hasParty(){
return this.partiesInfoList.length > 0;
},
partiesInfoListLength(){
return this.partiesInfoList.length;
}
},
methods: {
updatePartiesInfoList(additionalInfo){
// if(this.counter == 0){
// this.partiesInfoList.splice(0,1);
// }
this.partiesInfoList.push(additionalInfo);
this.counter++;
console.log(this.partiesInfoList);
console.log('The length of list is '+this.partiesInfoList.length);
},
deleteParty(resId){
// const resIndex = this.partiesInfoList.findIndex(
// res => res.id === resId
// );
// this.partiesInfoList.splice(resIndex, 1);
if(this.counter == 1){
return;
}
this.partiesInfoList.splice(resId, 1);
console.log('Index is '+resId);
console.log('after del');
console.log(this.partiesInfoList);
}
}
}
</script>
Actual Output Bug on screen : The adjacent element gets removed from the DOM. Say,I click on delete for the 'Second' but 'Third' gets removed. But, if there's an empty form element at the end of the v-for, then this one gets deleted.
The keys in your v-for should be an id, not an index. Vue ties each component to its key.
With keys, it will reorder elements based on the order change of keys, and elements with keys that are no longer present will always be removed/destroyed.
vue documentation
When you splice the partyInfoList item, Vue reregisters the indexes. The indexes cascade down, and since the there are now 2 items in partyInfoList instead of 3, the Vue component with an index of 2, or the last component, gets removed.
Here is an example:
// partyInfoList
[
// index: 0
{
id: 0,
fullName: 'first',
serviceAddress: '1',
preAuthorize: ''
},
// index: 1
{
id: 1,
fullName: 'second',
serviceAddress: '2',
preAuthorize: ''
},
// index: 2
{
id: 2,
fullName: 'third',
serviceAddress: '3',
preAuthorize: ''
},
]
Splice
// resId = 1
this.partiesInfoList.splice(resId, 1);
Resulting array
// partyInfoList
[
// index: 0
{
id: 0,
fullName: 'first',
serviceAddress: '1',
preAuthorize: ''
},
// index: 1
{
id: 2,
fullName: 'third',
serviceAddress: '3',
preAuthorize: ''
},
]
The array looks fine, but templates on the screen do not. This is because Vue saw that the partyInfoList item with an index of 2 does not exist anymore, so the component with :key="2" and all of its data was removed the DOM.
Your solution is actually very simple. All you need to do is change :key="index" to :key="data.id". This will tie your component to the data, not a dynamic value like an index.
I also don't know how you are setting the id on each partyInfoList item, but those must be unique for the keys to work.
This is what your new code should look like:
<base-card
v-for="(data, index) of partiesInfoList" :key="data.id"
ref="childComponent"
#add-parties="updatePartiesInfoList"
#delete-party="deleteParty(index)"
:lastElement="index === partiesInfoListLength - 1"
>
In updatePartiesInfoList just before pushing new object to partiesInfoList, check if this.partiesInfoList[0].fullName === 'dummy' returns true, then if the condition is truthy, execute this.partiesInfoList.splice(0, 1) to remove the dummy object from your array!
I cannot assign the value of the calculated user data to the object in the data, but the data is lost when the page is refreshed.
import Vuetify from "vuetify"
import {
UserData
} from "../../store/userModule";
import jsonDict from "../../jsonFiles/data.json"
import jsonfile from "../../jsonFiles/jsonfile";
export default {
name: "userProfileUpdate",
data() {
return {
selected: "Choose Province",
rules: [
value => !!value || 'Bu alan boş bırakılamaz',
value => (value || '').length <= 20 || 'Max 20 characters',
value => {
const pattern = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return pattern.test(value) || 'Geçersiz e posta'
},
],
options: jsonDict.Gender,
options2: jsonDict.educational_status,
educational_status: '',
gender: '',
birthday: '',
email: '',
phone_number: '',
username: '',
first_name: '',
last_name: '',
profileData: {
}
}
},
created() {
this.$store.dispatch('initUserData')
this.$store.dispatch('inijson')
},
I have tried many ways it disappears when the page is refreshed even creating computed data but somehow it could not keep the data on the screen after the page refresh
computed: {
genderq() {
for (var i in this.$store.getters.user) {
return this.$store.getters.user[i]
}
return this.$store.getters.user
},
userdata() {
for (const i in this.$store.getters.getUser) {
var data = this.$store.getters.getUser[i]
this.username = data['username']
//this.$store.dispatch('getJsonData',data['gender'])
return data
}
return this.$store.getters.getUser
},
},
Hey you could try using localStorage or sessionStorage, and add the mounted() property to your component (this property is fired when the component is mounted) and then you could affect your data() values from the localStorage for example
data() => { myData: 0 },
computed()=>{
storeValue(){
localStorage.setItem('data', this.myData)
}
},
mounted() =>{
localStorage.getItem('data') ? this.myData = localStorage.getItem('data') : null //null because in data() myData has a default value but you can say this.myData = 0
}
With the mounted lifecycle property and the browser storage, you will have a trace of every value you want to keep between 2 refreshes (look for both localstorage and sessionStorage as they don't last the same time), basically, you can have a method (not a computed) that stores the object you want in the storage, then you can call this method at the end of every computed property that modifies the data you want to keep between refreshes.
edit: here is a link to help you to understand the lifecycle of a vue component it might help you later too if you want to create more complex components, lifecycle diagram
Best regards
I have no idea if what I'm doing is correct or not, but here's a simplified version of what I'm trying to do:
I want to have 3 file inputs, with the 2nd and 3rd disabled until the 1st one has had a file selected.
I've tried to do is set the Vuex state variable to whatever the first file input is has selected, but upon doing that the other 2 inputs don't update their disabled state.
I have some file inputs that are created dynamically, like so:
Vue.component('file-input', {
props: ['items'],
template: `<div><input type="file" v-on:change="fileSelect(item)" v-bind:id="item.id" v-bind:disabled="disabledState"></div>`,
methods: {
fileSelect: function(item) {
store.commit('fileSelect', file);
}
},
computed: {
disabledState: function (item) {
return {
disabled: item.dependsOn && store.getters.getStateValue(item.dependsOn)
}
}
}
}
The data for the component is from the instance:
var vm = new Vue({
data: {
items: [
{ text: "One", id: "selectOne" },
{ text: "Two", id: "selectTwo", dependsOn: "fileOne" },
{ text: "Three", id: "selectThree", dependsOn: "fileOne" }
}
});
Now, notice the "dependsOn". In the Vuex store, I have a corresponding state item:
const store = new Vuex.Store({
state: {
files: [
{
fileOne: null
}
]
},
mutations: {
fileSelect(state, file) {
state.files.fileOne = file;
}
},
getters: {
getStateValue: (state) => (stateObject) => {
return state.files.findIndex(x => x[stateObject] === null) === 0 ? true : false;
}
}
});
Now, the above works when everything is first initialized. But once the first input has something selected, the other two inputs don't change.
I'm not sure how to update the bindings once a mutation of the state occurs.
I think you need to refactor your mutation to make the state property mutable, like this:
fileSelect(state, file) {
Vue.set(state.files[0].fileOne, file);
}
Well, I figured it out...
Because my state object is an array of objects, I can't just change one of the property's values with state.files.fileOne. I needed to do state.files[0].fileOne.