Enable loading state of an input field on a computed property - javascript

EDITED: code pen was added at the end of the post
How to eanable (change to true) the loading of an input field in a computed property?
In the example for demonstration that follows I get the 'error' unexpected side effect in "inputFilterParentsAndChilds" computed property.
The search field and the list
<template>
<q-input
label="Search"
v-model="searchValue"
placeholder="Minimum 3 characters"
:loading="loadingState"
/>
<q-list
v-for="pater in inputFilterParentsAndChilds"
:key="pater.id_parent"
>
<q-item>
<q-item-section>
<q-item-label>
{{ pater.name }}
</q-item-label>
</q-item-section>
</q-item>
<q-list
v-for="filius in pater.allfilius"
:key="filius.id_filius"
>
<q-item>
<q-item-section>
<q-item-label>
{{ filius.title }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-list>
</template>
Computed propriety
<script>
export default {
data() {
return {
loadingState: false,
searchValue: ''
};
}
}
computed: {
//filter the parent list with nested data (childs)
inputFilterParentsAndChilds() {
if (this.searchValue.length > 2) {
this.loadingState = true; // error!! unexpected side effect in "inputFilterParentsAndChilds" computed property
const filteredPaterArr = this.records.map((rows) => {
const filteredParent = rows.filius.filter(
({ name }) =>
name.toLowerCase().match(this.searchValue.toLowerCase())
);
const filteredChilds = rows.filius.filter(
({ title }) =>
title.toLowerCase().match(this.searchValue.toLowerCase())
);
if (filteredChilds.length) {
this.loadingState = false;
return { ...rows, filius: filteredParent };
} else {
this.loadingState = false;
return { ...rows, filius: filteredChilds };
}
});
return filteredPaterArr.filter((obj) => obj.filius.length);
} else {
return this.records;
}
}
}
</script>
About nested v-for list Filter nested v-for list
EDITED
CODEPEN https://codepen.io/ijose/pen/BaYMVrO
Why is loading important in my case?
If the list has hundreds of records the filter is slow and appears (wrongly) to have frozen, requiring a loading to inform the user that the filtering operation is still in progress

You don't, computed properties simply can't change state.
You can achieve similar effects by watching searchValue.
data () {
return {
searchValue: '',
isLoading: false,
filteredElements: []
}
},
watch: {
searchValue: {
immediate: true, // tells Vue to run handler function right after constructing the component
handler () {
// this function will be triggered when searchValue changes
this.isLoading = true
// give Vue chance to render the loader before doing heavy computation in 'filterElements' function
this.$nextTick(() => {
this.filteredElements = this.filterElements()
this.isLoading = false
})
}
}
Docs on nextTick
Docs on watchers
Edit:
Have you measured that it's really computing that makes your drop-down slow and not rendering? Take a look at the documentation on performance, especially "Virtualizing large lists"

Related

VueJS2: How to update objects in an array and pass back to parent?

I have a parent component that is passing down some API data to a child component in order to pre-populate some input fields. When the user changes some of this data on the child component, that child component emits the data back to the parent where we will process it for server submission on user form submit.
To handle the updates for processing, I am sending the child data back as an object which the parent stores in an array (array of objects). This array is what I am sending to the server for processing.
I am struggling with how to update object properties in an array of objects.
Codesandbox: https://codesandbox.io/s/romantic-mestorf-yc0i1h?file=/src/components/Parent.vue
Let me explain in detail. I have 3 components:
<App>
<Parent>
<Child />
</Parent>
</App>
App.vue:
<template>
<div id="app">
<form #submit.prevent="submitForm()">
<Parent
:localShortNames="formValues.localShortNames"
#save-form-data="saveFormData"
/>
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
import Parent from "./components/Parent.vue";
import data from "./assets/data.json"; // <--- will be an actual API request
export default {
components: {
Parent,
},
data() {
return {
formValues: {
localShortNames: data,
},
};
},
methods: {
saveFormData(x) {
// TO DO
},
submitForm() {
// TO DO: save data to server
},
},
};
</script>
Parent.vue:
<template>
<div>
<h5>List of Data</h5>
<Child
v-for="item in localShortNames"
:key="item.localSnameID"
:localShortName="item"
#save-form-data="saveFormData"
/>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
props: {
localShortNames: {
type: Array,
},
},
components: {
Child,
},
data() {
return {
newLocalShortNamesArr: this.localShortNames,
};
},
methods: {
saveFormData(x) {
let elementId = (el) => el.localSnameID === x.localSnameID;
const newArr = this.newLocalShortNamesArr.map((obj) => {
if (elementId) {
// I need to update the existing object in newLocalShortNamesArr with updated user submitted properties
// ...
} else {
// item does not exist, lets push it to newLocalShortNamesArr
// TO DO LATER: create "add new data" button for adding new objects to array
},
},
},
},
}
</script>
Child.vue:
<template>
<div>
<label for="name-input">Name:</label>
<input
type="text"
id="name-input"
v-model="formValues.name"
#input="$emit('save-form-data', formValues)"
/>
<label for="dialect-input">Dialect:</label>
<input
type="text"
id="dialect-input"
v-model="formValues.iso6393Char3Code"
#input="$emit('save-form-data', formValues)"
/>
</div>
</template>
<script>
export default {
props: {
localShortName: {
type: Object,
},
},
data() {
return {
formValues: {
localSnameID: this.localShortName
? this.localShortName.localSnameID
: null,
name: this.localShortName ? this.localShortName.name : null,
iso6393Char3Code: this.localShortName
? this.localShortName.iso6393Char3Code
: null,
},
};
},
};
</script>
Question: How to handle the update of objects in an array and "overwrite" those properties (name, and iso6393Char3Code) if the same id exists in the original array?
In the parent.vue, I was thinking of doing something like this, but I don't know:
saveFormData(x) {
// console.log(x);
let elementId = (el) => el.localSnameID === x.localSnameID;
const newArr = this.newLocalShortNamesArr.map((obj) => {
if (elementId) {
// I need to update the existing object in newLocalShortNamesArr with updated user submitted properties
// ...
} else {
// item does not exist, lets push it to newLocalShortNamesArr
// ...
}
});
Would Object.assign be better here vs map()? All I am trying to do is provide an array to the API called localShortNames that contains all the objects whether they have been updated or not. Hope this makes sense!
I have a codesandbox here with the above code: https://codesandbox.io/s/romantic-mestorf-yc0i1h?file=/src/components/Parent.vue
The first problem in Parent Component in saveFormData
if you want to check if the object exist in array or not you can use findIndex() method it loop through array and return the object index if exists else return -1 if not exist
if exist objectIndex will be greater than -1 and update that index with the object from child component and then we emit the array after updated to App.vue component
saveFormData(x) {
const objectIndex = this.newLocalShortNamesArr.findIndex((ele) => {
return ele.localSnameID == x.localSnameID
});
if (objectIndex > -1) {
// the object exists
this.newLocalShortNamesArr[objectIndex] = { ...x };
} else {
// this case need new html form for add new item in array
this.newLocalShortNamesArr.push(x);
}
this.$emit("save-form-data", this.newLocalShortNamesArr);
},
in App.vue component
we update the main array with the data that emitted from parent component
saveFormData(x) {
this.formValues.localShortNames = [...x];
},
i updated the codesandbox with the new code ..
I hope the solution will be clear to you
https://codesandbox.io/s/dreamy-clarke-v04wfl?file=/src/App.vue:595-734

Vue.js : Passing Props Down to Child Components to Update Styles in the DOM

I'm a beginner trying to get my app to pass props that set CSS styles down a chain to child components. I have a listener that checks for view port size, and as the window gets resized, it checks past a certain point and then swaps the css class and passes it down the chain..
I think I may be doing something incorrectly because my child components don't seem to be receiving the new styles and aren't updating in the DOM as I drag the window.
Here is my code.. I removed irrelevant code to make it easier to read:
Page_Listings.vue
<template>
<main>
<section>
<ListingRack
:prp_classes="rackClass"
/>
</section>
</main>
</template>
<script>
import ListingRack from './Listing__Rack.vue';
export default {
name: 'Front_Page__Panel',
data() {
return {
viewportWidth: window.innerWidth
}
},
methods: {},
mounted() { window.onresize = () => this.viewportWidth = window.innerWidth },
components: {ListingRack},
},
computed: {
rackClass: function(){
let theValue;
console.log('>> viewport width is now: ',this.viewportWidth)
if(this.viewportWidth > 1200) {
theValue = "grid_view";
console.log('>> grid view')
}
else {
theValue = 'card_view';
console.log('>> card view')
}
return theValue
}
}
}
</script>
Listing__Rack.vue
<template>
<div class="listing_rack" :class="classes">
<ul>
<li v-for="item in listings" :key="item.postId">
// I removed irrelevant code for hte sake of simplicity in this example.
// listings is a GraphQL returned array of data that generates a list of "listings".
<Listing
:prp_classes=classes
/>
</li>
</ul>
</div>
</template>
<script>
import Listing from './Listing.vue'
export default {
name: 'listing__rack',
data() {
return {
posts: [], // what we get from the database.
listings: [], // what we copy from the database.
classes: this.prp_classes
}
},
props: {
prp_classes: String
},
components: {
Listing
},
watch: {
classes: function(){
//just to check if we're receiving anything...
console.log(">> [Listing_Rack](watch)(classes) there was a change to classes!");
}
}
}
</script>
Listing.vue
<template>
<div :id=id
:class=classes
class="listing"
:style="backgroundStyle"
>
</div>
</template>
<script>
export default {
name: 'Listing',
data() {
return {
classes: this.prp_classes,
backgroundStyle: String
}
},
props: {
prp_classes: String
},
methods: {
checkClasses: function(){
if(this.classes === 'grid_view') this.backgroundStyle = 'background: center / cover no-repeat url(background.jpg);';
}
},
mounted: function() {
this.checkClasses();
},
watch: {
classes: function(){
this.checkClasses();
}
}
}
</script>
My console.logs on rackClass so I know the class swapping part is working, but all my subsequent child components don't seem to be updating accordingly..
Can someone tell me what I'm doing wrong? Is there a better way to do this? How come my props aren't being passed when I drag the window, and how can I dynamically set styles in the DOM?
Your code does not work because of the one big mistake (don't worry, many people do it)
You are passing your classes using props to child components. But instead of using this prop (prp_classes) directly in the child's template, you create an absolutely unnecessary classes property in the data()
Problem with that is that data() is executed only once when the component is created. If the value of the prp_classes prop changes later, classes property from the data() just holds the old value.
To fix this, remove unnecessary classes from the data and use the prop directly in the template...
...bit more explanation by example what is going on:
let prp_classes = 'card_view'
let classes = prp_classes
prp_classes = 'grid_view'
// prp_classes === 'grid_view', classes === 'card_view', prp_classes !== classes
// strings/numbers/Date ...all work the same
let o1 = { a: 1 }
let o2 = o1
o1.a = 2
// o1.a === 2, o2.a === 2, o1 === o2
More to study

How to access my state of array in another router page VUEJS, VUEX

I made a page with two routes one is the home page another is the config where you can decide what should be written to that container, now in the config panel I was able to get the input values, I put them to my state with map actions now I am getting an array with string values in it.
How can I access that array with mapGetters ? I link my code:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{ message }}</h1>
</div>
</body>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
export default {
name: "Home",
data() {
return {
// message: this.store.state.message
elementVisible: true
};
},
computed: {
...mapGetters(["message", "sec"]),
...mapGetters({
message: "message",
sec: "sec"
}),
createdDate() {
return moment().format("DD-MM-YYYY ");
},
createdHours() {
return moment().format("HH:mm ");
}
},
mounted() {
this.$store.dispatch("SET_MESSAGE");
this.$store.dispatch("SET_SEC");
setTimeout(() => (this.elementVisible = false), this.sec);
}
};
</script>
so what I have to do is to put to that{{message}} template my message which I received from the config panel and which is in my state right now sitting there as an array of string, for example, ["hello", "how are you"] that's how they are sitting there, so how can I grab the first one('hello') and write it out as a clean string and not as ["hello"] if you know how to grab all of them would be even better.
(RightNow it's just putting that whole array to my template)
Maybe I should something rewrite in my storejs file?
STOREJS:
const state = {
message: [],
// console.log(message);
sec: +[]
// other state
};
const getters = {
message: state => {
// console.log(this.state.message);
return state.message;
},
sec: state => {
return state.sec;
}
// other getters
};
const actions = {
setMessage: ({ commit, state }, inputs) => {
commit(
"SET_MESSAGE",
inputs.map(input => input.message)
);
return state.message;
},
setSec: ({ commit, state }, inputs) => {
commit("SET_TIMEOUT", inputs.map(x => x.sec).map(Number));
console.log(inputs.map(x => x.sec).map(Number));
return state.sec;
}
// other actions
};
const mutations = {
SET_MESSAGE: (state, newValue) => {
state.message = newValue;
},
SET_TIMEOUT: (state, newSecVal) => {
state.sec = newSecVal;
}
// other mutations
};
export default {
state,
getters,
actions,
mutations
};
what my homepage should do is that it writes out that message and there is a sec value which stands for the timeout, after that I want to continue with the second value in that array and when that times out I should want the third to be written out.. and so on.
Thank you!
Hello and welcome to Stack Overflow! Your message Array is being mapped correctly with mapGetters, but you're flattening it as a String when you put it inside the template with {{message}}, since the template interpolation logic covert objects to strings, the same as calling Array.toString in this case. You need to iterate it, i.e. using the v-for directive:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">
<span v-for="m of message" :key="m">{{m}}</span>
</h1>
</div>
</body>
</template>
Of course, if you only need the first item, you could show it directly using:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{message[0] || 'No message'}}</h1>
</div>
</body>
</template>

How to get Vuex to play nice with checkboxes that use an array?

I'm struggling to get checkboxes to work with Vuex. I have setup an example in this Codepen.
I have defined my checkboxes like this:
<v-checkbox
v-for="(item, idx) in $store.state.checkboxes"
:key="item.id"
:label="`Checkbox ${idx + 1}: ${item.name} [${checkboxes[idx].selected}]`"
:value="{ name: item.name, id: item.id, clicked: true }"
v-model="checkboxes"
>
</v-checkbox>
And these are the accompanying computed get and set:
computed: {
checkboxes: {
set(val){
console.log('checkboxes::set::val', val)
console.log('checkboxes::set::state', this.$store.state)
const triggeredBy = val.pop()
console.log('checkboxes::set::triggeredBy', triggeredBy)
this.$store.commit('updateCheckbox', triggeredBy)
},
get() {
console.log('checkboxes::set::get')
return this.$store.state.checkboxes
}
}
}
The get and set get called, but the set changes all checkboxes on the first click and on the second click the val is null.
I hope someone has time to have a look at it and point me in the right direction.
Thx!
Okay, for anyone interested I got it to work. I solved it with a wrapper component. Check out this Codeopen for the solution.
This is the wrapper that does the heavy lifting:
<template id="checkboxToggle">
<div>
<v-checkbox
:label="`Checkbox ${item.id}: ${item.selected}`"
v-model="value"
>
</v-checkbox>
</div>
</template>
And this is the computed property with the get and set:
const CheckboxToggle = Vue.component('checkboxToggle', {
template: "#checkboxToggle",
props: ['item'],
computed: {
value: {
get() {
console.log('checkboxToggle::value::get', this.item.selected)
return this.item.selected;
},
set(val) {
const check = val ? true : false
console.log('checkboxToggle::value::set::mutation', check, this.item.id)
this.$store.commit('updateFilter', { filter: this.item, check })
},
},
}
})
And this is the store mutation:
mutations: {
updateFilter: (state, { filter, check }) => {
console.log('mutations::filterCheck::item', filter, check)
const match = state.checkboxes.filter(item => item.id === filter.id)[0]
console.log('mutations::filterCheck::match', match)
match.selected = check
}
}
The inspiration came from this Stackoverflow question. It does it in a aroundabout way.

Vue / axios filter types of same type as single item

I have an app where I want to show a list of items. When you click on a single item, you're sent to it's "page", where its info is displayed.
These items have a type.
Beneath the single item info, I want to display all items with the same type, filtered from the list of all items. However I have no idea what to return in my filterItems()-method. Since the axios-calls are done with asyncData() I don't have access to singleitem.type, do I?
HTML:
<template>
<div>
<reusable-component v-for="item in singleitem" :key="item.id" />
<reusable-component v-for="item in filterItems(type)" :key="item.id" />
</div>
</template>
JS:
export default {
data() {
return {
singleitem: [],
allitems: []
}
},
asyncData() {
// Grab single item from ID supplied
return axios.get(`https://url/to/GetItemById${params.id}`)
.then((result) => {
return { singleitem: result.data }
})
// Grab all items
return axios.get('https://url/to/GetAllItems')
.then((result) => {
return { allitems: result.data }
})
},
methods: {
filterItems() {
// Filter items from all items that has same type as the singleitem
return allitems.filter(function(type) {
// Help!
})
}
}
}
I think this is a use case of computed properties [https://v2.vuejs.org/v2/guide/computed.html]
May be something like this:
computed: {
filterItems: function() {
return this.allitems.filter(item => item.type != singleitem.type);
}
}
So whenever the data changed. filterItems get (re)computed.

Categories

Resources