View not updating on Set changes in Vue.js - vue.js

In Vue.js, I have this pieces of code (with Typescript Vue.js 2 and classed components):
toggle(id: string): void {
if (this.selectedIds.has(id)) {
this.selectedIds.delete(id);
} else {
this.selectedIds.add(id);
}
}
and
get handledUsers() {
return this.users.map((user) => ({
...user,
selected: this.selectedIds.has(user._id),
}));
}
where selectedIds is a Set<string>.
The problem is that in Vue.js, Set is not modified as Array, so it seems that when I update the Set, Vue.js does not detects it as if I did a .splice() of an array. How can I make the view update?

You can add a key to the element or component that you want to be updated, and then change the key value. Changing the key value of an element or a component would make theme rerender.
Here is an example:
<div :key="refreshKey" >
some content...
</div>
Changing the value of refreshKey would cause this div to rerender and update it's content. So you should define a refreshKey in your components data, and then change it's value in your toggle method, for example from true to false.

Related

How to force Vue to update modified HTML

I use a custom directive to render LaTeX-code with KaTeX' renderMathInElement function. This, obviously, changes the component's innerHTML. I would like to re-run KaTeX once the content changes, but: The content never does!
A simple reproduction of the problem does not need KaTeX or directives and still shows, that reactivity works, but stops to work for the parts of a component with changed innerHTML:
<template>
<div>
{{content}}
<span ref="elem">{{content}}</span>
</div>
</template>
<script lang="ts">
import { Component, Ref, Vue } from "vue-property-decorator";
#Component({})
export default class Test extends Vue {
content = "Hello World!";
#Ref()
elem!: HTMLSpanElement;
mounted(): void {
// Without the following statement, Vue correctly re-renders the whole component after a second with the new content
// With this line, the update does not happen for the span element.
this.elem.innerHTML = "<b>Hello World!</b>";
setTimeout(() => {
this.content = "Greetings!";
}, 1000);
}
}
</script>
I suppose this is intended behavior - but that doesn't solve my problem. Is there some way to force Vue to replace all the component's DOM as soon as a re-render takes place?
You can use a key on your span, but if you don't want to tie it in with content, you can instead set it to a number, and increment it every time you want to make a change. Like so (I am not using TS here):
Set a key on your span:
<span :key="content_key">{{ content }}</span>
Then you can watch content and update the key accordingly:
watch: {
content() {
this.content_key ++;
}
}
In this way you can avoid setting the key to content directly.
Does this work for you?

How to reset all states when property value is changed from javascript?

I am using stencil framework. In my component I am using different states to trigger different events. I am also updating the property value of component from javascript.
I would like to reset all states value and reload the component with updated property value.
New property value is responsible for many actions like calling api, generating the cache key etc.
Can anyone suggest me the best approach to fulfill my requirement. Currently I am reset all the states in watcher method of property and call the componentWillLoad event but I am facing many issue in this approach.
Sample code
#Prop() symbol!: string;
#Watch('symbol')
symbolChanged(newSymbol: string, prevSymbol: string) {
if (newSymbol && newSymbol !== prevSymbol) {
this.resetStates();
}
}
resetStates() {
//Reset all state values here
this.componentWillLoad();
}
By setting key property on root element of render method would solve my issue like below code snippet.
uniqKeyId = uniqid.get();
#Prop() symbol!: string;
#Watch('symbol')
sysmbolWatcher(newSymbol: string, prevSysmbol: string) {
if (newSymbol != prevSysmbol) {
//update key attribute each switch of exchange
this.uniqKeyId = uniqid.get();
//Set default values based on properties as to consider this as fresh request.
this.setDefaultValues();
}
}
And in render method like below
render() {
return (
<section class="cid-minichart" key={this.uniqKeyId}>
//Render markup
</section>
);
}

Why in my nuxt-link doesn't reload page with same url?

If I’m on a page with the URL 'http://localhost:8080/item' and I’m clicking on the same link on this page, then the page does not reload.
I need to make that if I click on the same link, the page will reload.
My link:
<nuxt-link :to="/item">
Any insight will be welcome. Thanks!
Use key, something like:
<router-view :key="$route.params.yourCustomParam"/>
Also you can use something like:
<router-link :to="{ params: { yourCustomParam: Data.now } }" replace>link</router-link>
Remember to is passed router.push() and it accept an object also. Doing that, it is more declarative and controllable. I'm using this to decide if the page of component should be rerendered since they will based on id params obtained from URL entry, and my child component can still using nesting .
I recently tried to solve a similar issue and to overcome this I used Vuex with :key (ref).
Firstly, in your store you need a state property such as:
export const state = () => ({
componentUpdates: {
item: 0,
//can add more as needed
}
})
In general, you could use only one property across the app if you prefer it that way. Just remember that later on, the key value needs to be unique - that is in the case if you used this property for two or more components within one page, for example. In this case, you could do something like this :key="$store.getters.getComponentUpdates.item+'uniqueString'"
then a getter:
export const getters = {
getComponentUpdates(state) {
return state.updateComponent;
}
}
finally a mutatation:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update]++
}
}
Now we can utilise the reactive :key wherever needed.
But first in your nuxt-link lets add an event to trigger the mutation, note the usage of #click.native to trigger the click event:
<nuxt-link #click.native="$store.commit('updateComponent', { update: 'item'})" :to="/item">
Now in the item page, for example. Let's imagine there is a component that needs to be updated. In this case we would add :key to it:
<my-item :key="$store.getters.getComponentUpdates.item" />
That is it. As you can see this solution utilises the benefits of nuxt-link but also allows us to selectively update only parts of our page that need updates (we could update the entire page this way as well if needed).
In case if you needed to trigger the logic from mounted or initial load in general, then you could use computed property and :key to your div container, right inside the <template> of your page.
Add :key to the div:
<template>
<div :key="$store.getters.getComponentUpdates.item"></div>
</template>
Create computed property:
computed: {
updateItemPage() {
//run your initial instructions here as if you were doing it in mounted then return the getter
this.initialLoadMethod()
return this.$store.getters.getComponentUpdates.item
}
}
The final touch, which is not crucial but can be implemented in order to reset the state property:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update] >= 10
? state.componentUpdates[payload.update] = 0
: state.componentUpdates[payload.update]++
}
}

How to set focus to a vue.js sfc root-element once it is visible (v-show is toggled)

I have a side-nav component that is hidden by default using v-show.
A click event in an external component sets a flag in vuex to toggle the side-nav.
How can I set focus to the side-nav component's root div once it's displayed?
I am trying to use this focus-on focus-off technique to hide the side-nav
Maybe something like this:
export default {
watch: {
someVarFromVuex(value) {
// Don't focus unless the value got switched on
if (!value) return;
// Make sure to add the ref to your template.
// You could also use an id.
const $input = this.$refs.input.$el;
// Just in case the input somehow doesn't exist...
if ($input) {
this.$nextTick(() => {
$input.focus();
})
}
},
},
};
Note that if you are actually trying to focus a div, then it will need to have a tabindex.

Vue does not update items in v-for from Vuex with dynamic component

We have a dynamic component for tab body, which defined as
<component :is="currentTab.itemType" :itemId="currentTab.itemId"></component>
Template has a span, which reflects itemId - it changes every time when the currentTab changed in tabs host component.
Each component of tab.itemType has Vuex module, belongs to it specific type.
For example, there is store module product with described state:
{
products: { [itemId: string]: IProduct }
}
When component created or itemId changed, it tries to run load action and put loaded product to products of vuex state.
So, there is Vue computed property, looks like
#State(productNamespace)
state: IProductState;
get currentProduct() {
return this.state.products[this.itemId];
}
or even
#Getter(GetterNames.GET_PRODUCT_BY_ID, bindingOptions)
getProductById: (itemId: string) => IProduct;
get currentProduct() {
return this.getProductById(this.itemId);
}
Each product has an attributes list, which is iterated by v-for with :key.
<v-list :key="itemId"><!-- itemId has no effect there -->
<v-list-item v-for="attribute in currentProduct.attributes" :key="attribute.id">
...
</v-list-item>
</v-list>
The problem is:
when we change itemId, the attributes list displays all attributes from last added product and does not refresh it when switching to previous "tabs" with another itemId but the same itemType.
I've tried to set :key of parent div as itemId but with no effect.
When I set :key to <component>, vuex state becomes broken.
Vue version is 2.6.10
UPDATE:
It does not work with simple property of product too:
{{ currentProduct.name }}
Summary:
There is the itemId property in. And computed property which depends on it. So computed property does not reflect changes when itemId prop changed while Vuex collection does not changed.
Confirmed:
Computed property renews only when state.products collection changed. I've emulate this by run createProduct action for each tab switching. Collection in vuex state accepts unwatched product stub and reflect changes to legal currentProduct with given itemId
UPDATE 2: component with watcher. Still no way...
#Component
export default class Product extends Vue {
#Prop({ type: Object, required: true })
readonly tabItem: ITabItem;
#State(productNamespace)
state: IProductState;
itemId: string;
created() {
//...
this.initCurrentProduct();
}
// No changes until state.products was changed.
get currentProduct(): IProduct | {} {
if (!this.state) return {};
return this.state.products[this.itemId];
}
#Watch('tabItem')
onTabItemChanged()
{
DEBUG && console.log('Tab changed: keep moving!');
this.initCurrentProduct();
}
private async initCurrentProduct() {
const { isNew, itemId } = this.tabItem;
if (itemId === this.itemId)
return;
DEBUG && console.log('ItemId changed.');
this.itemId = itemId;
// ...
}
// ...
}
Okay so the property you're passing to the dynamic component is currentTab.itemId which means itemId is actually an element in the currentTab object not the root Vue data object?
Vue does not track nested objects by default, it will only trigger redraw when the entire object is changed (for example if you do something like currentTab = {...}). You can either:
Use a watcher on currentTab with deep: true attribute: https://v2.vuejs.org/v2/api/#watch, and then trigger redraw with this.$forceUpdate whenever it is called.
Move itemId to the root of data and just update it from there
in your vuex mutation
let items = [...state.items]; // create a new copy
// mutate it
items.map(item => item.selected = true);
// return the new copy
state.items = items;