How to change data in row without refreshing - vue.js

Bootstrap vue table does not detect when there is a change. It shows when I refresh. How to change data in row without refreshing? After I update the data, I want the data to be updated without making a get request again.
My Data:
items = [
{ id: 1, name: 'Veli' },
{ id: 2, name: 'Berkay' },
{ id: 0, name: 'Mehmet' },
]
<b-table
:items="person"
:fields="fields"
/>
<template>
<form
v-for="(person, index) in persons"
#submit.prevent="submit"
>
<input v-model="person.name">
</form>
</template>
<template>
<div v-for="(person, index) in persons">
<span :key="index">{{person.name}}</span>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
persons: []
}
},
beforeCreate() {
this.$store.dispatch("loadPerson").then((response) => {
Vue.set(this, "persons", response);
})
},
methods: {
submit() {
axios.put('/person', this.person)
}
},
}
</script>

I think like this, we normally re-render the DOM with state. So changes in state cause the DOM to be updated.
If you don't have a lot of data, you can only update the changed data with the map method on the variable you hold your data, so I think the DOM will be updated as well.
However, if you receive a lot of data (eg thousands, millions), I think it would be better to send a GET request to the database again.
Because it would be unreasonable to use array methods on millions of data.

Related

Vue child component not rerending after parent component's data value updates

I am trying to have a child component update its props that were passed from the parents at the start of the rendering. Since the value is coming from a fetch call, it takes a bit of time to get the value, so I understand that the child component will receive a 'null' variable. But once the fetch call is completed, the value is updated but the child component still has the original null value.
During my search for a solution, I found that another way was to use Vuex Stores, so I implemented it with the count variable and had a button to call a commit and later dispatch with an action function to the store to increment it's value but when the increment happens, it doesn't show the new value on the screen even though with console logs I confirmed it did change the value when the function was called.
I guess I don't fully understand how to update the value of a variable without reassigning it within it's own component or having to call a separate function manually right after I change the value of a data variable.
App.vue
<template>
<div id="app">
<div id="banner">
<div>Title</div>
</div>
<p>count: {{count}}</p> // a small test i was doing to figure out how to update data values
<button #click="update">Click </button>
<div id="content" class="container">
<CustomDropdown title="Title Test" :valueProps="values" /> // passing the data into child component
</div>
</div>
</template>
<script>
import CustomDropdown from './components/CustomDropdown.vue'
export default {
name: 'App',
components: {
CustomDropdown,
},
data() {
return {
values: null
count: this.$store.state.count
}
},
methods: {
update() {
this.$store.dispatch('increment')
}
},
async created() {
const response = await fetch("http://localhost:3000/getIds", {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
});
const data = await response.json();
this.values = data // This is when I expect the child component to rerender and show the new data. data is an array of objects
console.log("data", data, this.values) // the console log shows both variables have data
}
}
</script>
CustomDropDown.vue
<template>
<div id="dropdown-container" class="">
<b-dropdown class="outline danger" variant="outline-dark" :text="title" :disabled="disabled">
<b-dropdown-item
v-for="value in values"
:key="value.DIV_ID"
href="#">
{{value.name}}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
data() {
return {
values: this.valuesProp
}
},
methods: {
},
created() {
console.log("dropdown created")
console.log(this.valuesProp) //Always undefined
}
}
</script>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state() {
return {
count: 0,
divisionIds: []
}
},
mutations: {
increment (state) {
console.log("count", state.count)
state.count++
}
},
actions: {
increment (state) {
console.log("count action", state.count)
state.commit('increment')
}
}
})
data in your child component CustomDropdown.vue is not reactive: therefore the value of this.values is not updated when the prop changes. If you want to alias a prop, use computed instead:
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
computed: {
values() {
return this.valuesProp;
}
},
created() {
console.log("dropdown created");
}
}
If you want to console log the most updated values of this.valuesProp, you will need to watch it: the same if you want for this.values.
One thing you can do is to use a v-if in your child component to only render it after you get your result from you api.
It would be something like:
<CustomDropdown title="Title Test" :valueProps="values" v-if="values"/>
This way you would make sure that your child component gets rendered only when values are available.
It would only be a bad solution if this api call took so long and you needed to display the child component data to the user before that.
Hey you can simply watch it your child component
watch: { valuesProp: function(newVal, oldVal) { // watch it if(newVal.length > 0) do something }
it will watch for the value changes and when you get your desired value you can perform whatever hope it will help you you dont need store or conditional binding for it.

Vue: Updating a data property triggers re-evaluation of entire data?

I've been working on Vue project for almost a year, and I've just observed unexpected behavior below for the first time today...
Here is a link to code sandbox:
https://codesandbox.io/s/2bnem?file=/src/App.vue
And a code snippet from above link:
<template>
<div>
<div>{{a}}</div>
<div>{{translator(b)}}</div>
<input v-model="a" />
</div>
</template>
<script>
export default {
data() {
return {
a: 'a',
b: 'b',
}
},
computed: {
translator: function() {
return function(value) {
console.log(`translated...: ${value}`)
return value
}
}
}
}
</script>
Now every time I hit the key on input, the translator is triggered.
Is this a correct behavior?
If so, what is the cause of this problem or a background reasoning of this behavior?
Note that my vue version is 2.6.14(latest).
Your original issue is that you were attempting to use a method to render parts of your template. Methods used like this will execute for every update cycle, regardless of what changed.
The better solution is to use a computed property. Here's a somewhat dynamic example that wraps each of your data properties with a computed translator_x property
<template>
<div>
<div>{{ a }}</div>
<div>{{ translator_b }}</div>
<input v-model="a" />
</div>
</template>
<script>
const defaultData = {
a: "a",
b: "b"
}
export default {
data: () => ({ ...defaultData }),
computed: Object.fromEntries(Object.keys(defaultData).map(k => [
`translator_${k}`,
vm => {
console.log("translated...", k)
return vm[k]
}
]))
};
</script>
Each translator_x property will only be evaluated if the underlying data property is changed.

Vue - how can i update the default value of an input text field?

I'm loading a form component more times in the same page, that's because i have more forms for different tasks, so each form has different parameters.
Html page:
<div id="app">
<myForm formType="buy"></myForm>
<myForm formType="sell"></myForm>
<myForm formType="submit"></myForm>
<refreshAmount></refreshAmount>
</div>
And this is the form component:
<template>
<div>
<div v-if="formType=='buy'">
<form #submit.prevent="formSubmit()">
<input type="text" class="form-control" value="testetete" v-bind:value="amount">
<button v-if="side==='buy'" class="btn btn-primary" style="width: 100%">BUY</button>
<p>Available amount: {{$store.getters.amount}}</p>
</form>
</div>
...
</div>
</template>
<script>
export default {
props:{
formType:{
type:String,
default:'buy'
},
},
mounted() {
console.log('mounted')
},
data() {
return {
amount: this.$store.getters.amount
}
},
methods: {
...
}
}
</script>
Then i have the following store:
<script>
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
amount: 0
},
mutations: {
refreshAmount(state) {
fetch('SOME-URL')
.then(response => response.json())
.then(data => {
state.amount = 100
//state.amount = data['amount']
})
}
},
getters: {
amount: state => state.amount,
}
})
</script>
And finally, the refreshAmount component:
...
mounted() {
this.$store.commit('refreshBalance')
}
...
Basically, i need to show an amount in the form component. This amount is retrieved from my backend, and since i'm loading the form component 3 times, i would call my backend 3 times while i only need to call it once, so i decided to create the component refreshAmount that would call it once and pass it to the form components using a Vuex store.
The problem with my code is the following:
When i load the page, i'll see Available amount: 100 on all the 3 components, so that works; but in the default value of the text input form the value is 0. Why is that? Why isn't the value inside the input text field updated while <p>Available amount: {{$store.getters.amount}}</p> is updated?
Tl;dr: i'm using Vuex to set the value of a variable in my components, when i load the variable between a <p> tag the value is refreshed, while inside the input field the value of the variable stays the same.
amount is not updated because it's a data property, which only gets initialized when the component is first set up and not updated after. What you need is a computed property, which will keep track of the changes in the Vuex store. So instead of:
data() {
return {
amount: this.$store.getters.amount
}
}
you can do:
computed: {
amount() {
return this.$store.getters.amount
}
}

Child component does not re-render when props change in store

I am using Vue-js with require-js. I am trying to get data from vuex store into my cart component and render a component for each item in the store. But when I trigger a mutation from my body component to change the store, the data is being changed and the props of my cart component change, but the UI does not re-render.
This is my store:
state: {
users: {
user1: {
item: { date:null }
}
}
}
mutations: { setDate:function(state,payload){
var newState = state.users;
newState[user][item].date = payload.date
state.users = Object.assign({},newState)
} }
This is my cart component:
<template>
<div v-if="activeStep==1">
<p> Service Time: {{service.ServiceTime}} Min.</p>
<p>Date: {{service.date || 'Not Selected'}} </p>
<p>Time: {{service.time || 'Not Selected'}} </p>
<p>Prefered Staff: {{service.staff}} </p>
</div>
</template>
<script>
define(['Vue','vuex'],function(Vue,vuex){
return {
template: template,
computed: vuex.mapState(['activeStep']),
props: ['service'],
}
})
</script>
This is the parent of my cart component:
<template>
<div class="cart-user-body" >
<div class="cart-service" v-for="(service,key,index) in users[user]" :key="index">
<div class="cart-service-body">
<service-book-details :service="service"></service-book-details>
</div>
</div>
</div>
</template>
<script>
define([
'Vue','vuex','vue!./serviceBookDetails'
], function(Vue,vuex,serviceBookDetails) {
return {
template:template,
components: {
'service-book-details': serviceBookDetails
},
props: ['user'],
computed: vuex.mapState(['users']),
}
});
</script>
This is how I am triggering the mutation from my body component:
addDate(e) {
var payload = {
date: moment(e, "DD/MM/YYYY").format("Do MMMM YYYY"),
id: this.$data.class,
name: this.username
};
this.$store.commit("setDate", payload);
},
I even tried using Vue.set(state,'users',newState) but the UI does not re-render.
I have checked the Vue dev tools and I see that upon triggering the mutation, the props of my cart component have updated but it does not show on the UI.
If I try using getters, the key to the object does not exist as my store does not have the required data until user interacts with UI and adds data. And my cart component is always showing since the start so it shows me an error saying cant read property item of undefined.
Am I doing anything wrong or is there a different way to make it work.
You can't use array indexing for setting values with Vue. It is a restriction caused by Javascript.
This will not be reactive if user and or item did not exist when you created your store.
newState[user][item].date =
Instead, you need to use:
Vue.set(object, key, value)
In your case, you first need to ensure you set user and item with that method before assigning to date.

Vuex strict mode throws exception while updating nested array

I have a page where user can edit multiple segments. Every segment has a name and an array of filters that he can added/removed.
{
segments: [
{
name: 'Mac',
filters: [
{
field: 'os',
value: 'mac'
},
{
field: 'browser',
value: 'chrome'
}
]
}
]
}
Index.vue
<template>
<div class="segments">
<segment
v-for="segment in segments"
:id="segment.id"
:name="segment.name"
:filters="segment.filters">
</segment>
</div>
</template>
<script>
import Segment from './Segment'
export default {
vuex: {
getters: {
segments
}
},
components: {
Segment
}
}
</script>
Segment.vue
<template>
<div class="segment">
<input type="text" class="name" v-model="name" />
<filters :filters="segment.filters"></filters>
<button #click="saveSegment()">Save</button>
</div>
</template>
<script>
import Filters from './Filters'
export default {
props: ['id', 'name', 'filters'],
vuex: {
methods: {
updateSegment({dispatch}, id, segments) {
dispatch(SEGMENT_UPDATE, id, segment)
}
}
},
methods: {
save () {
this.updateSegment(this.id, {
name: this.name,
filters: this.filters
})
}
}
}
</script>
Filters.vue
<template>
<ul class="filters">
<li v-for="filter in filter">
{{ filter.name }} <button #click="remove($index)">Remove</button>
</li>
</ul>
</template>
<script>
export default {
props: ['filters'],
methods: {
remove (index) {
this.filters.splice(index, 1)
}
}
}
</script>
Every time I remove a filter, I get Do not mutate vuex store state outside mutation handlers. error. And I know why, because no matter how many times I pass filters array through components properties, they still remain reactive and their change in Filters component propagates down to the vuex store that throws an error.
Same will be with segments names in inputs. But there is an example how to handle forms in docs.
But how can I make filters work? Make separate stores for filters? But it will be a mess because there will be multiple segments with their own filters on the page... I'm stuck :(
As well as I understood from the conversation in the vuex repo itself, the only way and the best decision is to deep clone the filters array and then just use it as a local variable.
It has to be done because arrays in JS always passed by reference and deep clone unbinds my filters array from the one in the vuex store.
This can be achieved by JSON.parse(JSON.stringify()), _.cloneDeep() or any other similarmethod.