vue3 array value problem between components - vue.js

i have a strange problem with arrays in vue3
When loading the components, a start array is transferred, in the components the values ​​are taken over with a watcher if the start array changes. that works wonderfully. the whole thing is a little game, the user moves a cone on a certain route.
I switch to another component by changing a page number. the component with the game should actually be completely from the dom, I don't see anything in the tree either.
<transition name="fadepage" mode="out-in" >
<div v-if="getPage()==3">
<VueGameIntro :content="content" :level="level" :kontrastswitch="kontrastSwitch" :typoswitch="typoSwitch" :levelcolor="levelColor" #mqttMessage="changeMqttMessage"></VueGameIntro>
</div>
</transition>
<transition name="fadepage" mode="out-in" >
<div v-if="getPage()==4">
<VueGame :content="content" :level="level" :playerlist="playerList" :startpointlist="startPointList" :levelendstate="levelEndState" :activeplayerlist="activePlayerList" :kegelmoved="kegelMoved" :kontrastswitch="kontrastSwitch" :id="id" #mqttMessage="changeMqttMessage" #changeCurrPoint="changeCurrPoint"></VueGame>
</div>
</transition>
<transition name="fadepage" mode="out-in">
<div v-if="getPage()==5">
<VueGameOutro :content="content" :level="level" :levelendstate="levelEndState" :kontrastswitch="kontrastSwitch" :typoswitch="typoSwitch" #mqttMessage="changeMqttMessage"></VueGameOutro>
</div>
ok, when the first level has been played it changes to another component VueGameOutro, then it goes back to the actual game components with a new level. Suddenly I have the feeling that this array exists twice.
in the console i see different values ​​than in the output on the screen: {{ currPointList }} shows something different than this.currPointList. in the vue dev plugin, however, are the values ​​​​as well as the screen output. but the component definitely has a different starting point than the one in the currPointList.
the value in the console seems to have been the latest from the previous level.
Is that possible? how can I be sure that a component is completely gone?
are there arrays that have different values ​​for string or number?
##########################################################
edited
that's the console output:
currPointList unmounted 25,0,0,0,
currPointList mounted 8,0,0,0,
GAME_MOVE currPointList #move,1,3,1 25,0,0,0,
currPointList before move 25,0,0,0,
currPointList move point updated 3,0,0,0,
as you can see, the 25 is in front of the unmount, then the component is reloaded and gets the 8 once via a property form the parent comp
then a move command arrives via websocket, an mqtt component receives the message and sends it to the game via MITT emitter. there's no other place to change currPointList. the first value jumps back to 25.

the problem occurred because the emitter.on("eventname" ...) was not deleted when removing the components. I also changed the emitter.on to an arrow function.

Related

Prevent list display filter from resetting v-list selection

Version info: Vuetify 2.6.3, Vue 2.6.14, Nuxt 2.15.8
I'm making a custom component that is supposed to be somewhat similar to v-autocomplete, except that it's rendered as bottom sheet. If user enters a display filter into v-text-field, the option list (v-list) is supposed to display only those options that match the filter.
In overall it works fine except one use case: let say the list has 5 items (aa, bb, cc, dd, ee) and user selected bb and cc from the list. Now, v-list-item-group's model selectedItems contains the 2 selected items bb and cc, perfect! However, when user enters b into display filter, the already selected item cc will be auto deleted from selectedItems. I can't tell if selectedItems change is caused by filter or by user selection. Is there a way to maintain the selection in model?
I'm considering a hack - if an item is selected, keep it in filteredChoices even if it does not match the filter. This behaviour is bearable but UX wise not as intuitive as the filter of v-autocomplete.
The simplified structure looks like the below:
<template>
<v-bottom-sheet scrollable>
<v-card>
<v-card-text>
<v-list>
<v-list-item-group
v-model="selectedItems"
:mandatory="!optional"
:multiple="multiple"
>
<v-list-item
v-for="item in filteredChoices"
:key="item.value"
:value="item"
>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card-text>
<v-text-field
v-model="filterInput"
placeholder="filter choices..."
hide-details
></v-text-field>
</v-card>
</v-bottom-sheet>
</template>
<script>
...
filteredChoices() {
if (this.filterInput == null) {
return this.allItems
}
return this.allItems.filter((item) => {
return item.label
.toLocaleLowerCase()
.includes(String(this.filterInput).toLocaleLowerCase())
})
},
...
</script>
How I reach the solution:
I was quite new to front-end stuff. Previously, when I was learning how to implement v-model support for custom component, all web resources I came across say the same thing - bind inner component's value props to custom component's value props. However I just discovered that this is merely one of the ways rather than a must. With that new learning, more possibilities pop up in my mind and one of them lead me to below solution.
Solution:
Decouple the custom component value from bottomsheet's list
Bind the model of inner v-autocomplete to an array data, say internalValue. (this inner component is not included in question's template for simplification)
Bind the model of inner v-list-item-group (in bottomsheet) to a separate data, say bottomSheetSelections.
Update the custom component value based on user actions in bottomsheet
Add a watcher to bottomSheetSelections array:
if the array grows, it means the user has selected more item. We should push the additional item to internalValue.
if the array shrinks:
if the missing item is still there in filteredChoices, the removal is triggered by user de-selection. We should remove this item from internalValue.
else, we consider the removal is triggered by list filter. No action is needed.
Restore user selection in bottomsheet on clearing filter
Add a watcher for filteredChoices. Whenever the array grows, if the additional choice exist in internalValue, we should push it to bottomSheetSelections.
Summary
Strictly speaking, this doesn't solve the ask of question's title - the list selection in bottomsheet (bound to v-list-item-group) is still getting reset. However, at least we're able to restore it in bottomsheet on clearing filter.
More importantly, this solution achieved the objective mentioned in question details - retain the user selection value. We kept it in a separate data internalValue.
I haven't test this solution with long list of data. My guts feeling is that there could be more efficient solution. Please share it if you have a better solution.

How to cache filtered out elements to prevent re-rendering inside a v-for?

I’ve been working on a Vue.js application that manages user’s notes and allows to easily look them up by title, tags, etc. So the primary view of a logged-in user is a list view of Card components. I’m using v-lazy and group-transition components to enhance usability. Everything works fine, but…
When the set of notes becomes larger (100+ Cards), one can notice (especially on mobile devices) that filtering slows down. After some debugging I found that the filtering calculations are not the heavy part but the re-rendering of filtered components is the real problem.
Let’s say, with the following scenario:
there's a list of 150 Card components
the user scrolled down to the end of the page, so that 150 Cards are in fact rendered and one of them contains tag: tag-1
if the user clicks on tag-1, 149 Cards get filered out, the 1 result
remains Vvue debug tool shows that 149 Card components get destroyed as the computed method visibleCards contains only one element)
if the user unclicks the tag-1, all 149 Cards are re-rendered again
(Vue debug tool shows that 149 Cards are fully re-created again)
Below, there’s the meat of my Card list displaying view:
<v-row justify="center" dense class="px-sm-2">
<transition-group
name="list"
tag="div"
class="cardListTransition col-12 pa-0"
>
<v-row
:id="'columnid-' + index"
v-for="(card, index) in visibleCards"
:key="card.id + ':' + card.created"
justify="center"
class="snipSingleRow"
>
<v-col lg="10" xl="8" class="pt-0 mb-lg-4 mb-xl-4">
<v-sheet min-height="50" class="fill-height" color="transparent">
<!-- 'threshold: indicates at what percentage of the target's visibility -->
<!-- the observer's callback should be executed. -->
<v-lazy
v-model="card.active"
:options="{
threshold: 0.8,
}"
class="fill-height"
transition="list"
>
<Card :card="card" />
</v-lazy>
</v-sheet>
</v-col>
</v-row>
</transition-group>
</v-row>
where: visibleCards is a result of a computed method inside of which filtering occurs.
Is there any way - I’ve experimented with v-once or keep-alive, etc. with no help - to save / cache the filtered out components to prevent them from re-rendering once the filters are cleared?
PS.
Stripping down the list i.e. removing v-lazy or group-transition doesn’t solve the problem.
I’d appreciate any help / suggestions or further reading on how to solve my problem.

Bootstrap Table row-clicked event

I'm using a <b-table> component in a Vue project and I need to define a function to be triggered when a row is clicked. I heard that there is a #row-clicked property in b-table so I tried to use it and I can put simple functions inside of it, I mean I can get console prints etc but nothing more complicated...
I want two functions to be executed when a row is clicked. The functions are now bound to a button inside of every row, but I dont want use it like this, I just need to click the related row to make them work. Here is the current version of the b-table component;
<b-table
:items="displayByType"
:fields="displayColumn"
responsive="sm"
:per-page="perPage"
:current-page="currentPage"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:filter="filter"
:filter-included-fields="filterOn"
#filtered="onFiltered"
hover
#row-clicked="test"
>
And here is the two functions that I want to be triggered when a row is clicked...
toggleRightBar(){
document.body.classList.toggle("right-bar-enabled");
}
changeRightBarContent(row){
this.$store.dispatch("rightbar/changeRightBarInfo", tableData[row]);
},
There is a JavaScript folder that I export column names and row data. I also a have Rightbar, when a row is clicked, I want to enable the rightbar so that the user can see detailed information about that specific row.
At last, here is the button that I use to make these two functions work;
<template #cell(detail)="row">
<button #click="toggleRightBar(); changeRightBarContent(row.index);" class="btn btn-outline-primary toggle-right">
<i class="bx bx-detail toggle-right"></i>
</button>
</template>
Basically, I dont want to use this button, instead, I want to click the row itself. Every row's rightbar information will be uniqe, my function does that. I just dont know how to use these 2 functions inside #row-clicked
Thanks in advance.
The row-clicked event will pass 3 parameters to your function.
The first being the item specific to the row that was clicked. The second will be the index of the row, and the third is a native click event.
This means you can use the arguments keyword to get the index and pass it to your function.
#row-clicked="toggleRightBar(); changeRightBarContent(arguments[1])".
Alternatively you can create a third method which will call the other two.
<b-table #row-clicked="onRowClicked" />
{
onRowClicked(item, index, event) {
this.toggleRightBar();
this.changeRightBarContent(index);
}
}
You can read more about the #row-clicked event on the docs component reference section.
https://bootstrap-vue.org/docs/components/table#comp-ref-b-table-events

Vue and "vuedraggable", update data in subcomponents?

I have a list which is draggable and I use there "vuedraggable" component.
Inside this list I have 3 subcomponents.
1 input and 2 selectboxes, I have there #change to update informations.
If I do the order of "tasks", it works perfectly and I send to backend correct data, but in frontend in subcomponents is the data not updated. In selectbox and input box I see the data on the same place.
My vuejs example:
<draggable v-model="tasks" draggable=".task">
<div v-for="task in tasks" class="task">
<task-title-update :task="task"></task-title-update>
<task-assignee :task="task"></task-assignee>
<task-priority :task="task"></task-priority>
</div>
</draggable>
Is there possible to keep this subcomponents and update the data correctly?
You need to listen on the event with one of the events that vue-draggable provides, try using #end or #start, then you can put the logic from the #change into those events.

Keep list components alive in Vue

I have a list of components that I render using v-for. Not all the components are shown simultaneously. I page the the array of rendered components by using slice.
These components shouldn't be rerendered, as some of them have user inputted data and some of them do network related tasks.
I tried to use <keep-alive>. However, this renders only the first component.
<keep-alive>
<component :is="component.type" :key="component.id" v-for="component in components">
</keep-alive>
How do I keep a dynamic list of components alive?
<div v-for="comp in compList" :key="'container'+comp.keyId" >
<keep-alive>
<component :is="comp.type" :key="comp.keyId"></component>
</keep-alive>
</div>
this above works for me . Pushing elements to compList correctly creates new instances of their respective components. Moving an element's index within the array , maintaining key, does not call destroy/create and maintains state within each component
Tested the answer above in a fiddle and doesn't work. https://jsfiddle.net/z11fe07p/680/
<div v-for="component in myComponents" :key="component.id" >
<keep-alive>
<component :is="component.type"
:name="component.name">
</component>
</keep-alive>
</div>
Also i would avoid using vue reserved words such as components because there is a components key in the vue instance which tells what components the instance is using.
I read source code of Vue's <keep-alive>, and I created new Component which works very well with list.
package name is vue-keep-alive-global. Here is a link, https://www.npmjs.com/package/vue-keep-alive-global
How to use :
<KeepAliveGlobal>
<YourComponent
:key="uniqueKey"
/>
</KeepAliveGlobal>
With Array,
<template v-for="(item, index) of array">
<KeepAliveGlobal :key="`blah-blah-${index}`">
<YourComponent
:item="item"
:key="`your-component-${index}`"
/>
</KeepAliveGlobal>
</template>
KeepAliveGlobal will cache component by key.
There's note at Vue docs about your use case
Note, <keep-alive> is designed for the case where it has one direct
child component that is being toggled. It does not work if you have
v-for inside it. When there are multiple conditional children, as
above, ` requires that only one child is rendered at a
time.
https://v2.vuejs.org/v2/api/#keep-alive
Try v-once directive instead.
https://v2.vuejs.org/v2/api/#v-once