Getting part of the page to display updated data in vue - vue.js

I'm using vue to create a page where I list all users and if I click on the edit button the details of that user then gets shown
next to the list.
What I'm trying to do is, if I update a user and click save then the user details in the list needs to change.
The problem I'm having is that I'm not able to get the details to change in the list after I've saved.
My vue
<template>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-7">
<table class="table table-striped table-sm mt-2">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="user in displayAllUsers">
<td>{{ user.name }}</td>
<td>
<button class="btn btn-sm btn-success" #click="manageUser(user)">Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-5" v-if="user != null">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">Manage {{ user.name }}</h4>
</div>
<div class="card-body">
<table class="table">
<tr>
<th>Name</th>
<td>
<input type="text" v-model="user.name">
</td>
</tr>
</table>
</div>
<div class="card-footer">
<button #click="updateUser()"class="btn btn-success"><i class="fa fa-save"></i> Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {
},
data: function () {
return {
users: [],
user: null
}
},
computed: {
displayAllUsers(){
return this.users;
}
},
methods: {
manageUser(user){
axios.get('/admin/user/'+user.id).then((response) => {
this.user = response.data.user;
});
},
updateUser(){
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
this.users = response.data.user;
});
}
},
mounted() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
}
</script>

There are two possible solutions.
The first is to run this code at the end of the updateUser method:
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
The second is to use a state manager like Vuex.
The first scenario will fetch again your users data from the remote API and will update your view with all your users.
With the second scenario, you will handle your application state way much better than just using the data attribute of your page module, but in the background, it is more or less the same as the first solution I suggest.
To update the current user only in the table you could do something like that at the end of the updateUser method:
let userIdx = -1;
for(let idx = 0, l = this.users.length; idx < l; idx++) {
if ( this.user.id === this.users[idx].id ) {
userIdx = idx;
break;
}
}
if ( -1 !== userIdx ) {
this.users[userIdx] = this.user;
this.user = {};
}
Other than your problem, it seems like you don't need this code:
computed: {
displayAllUsers(){
return this.users;
}
},
You could remove this code, and instead use this code in the HTML part:
<tr v-for="user in users">

For your updateUser function you could just return the modified user in the same format that you have for all the users in you user list and update the user by index. This is presuming that the user you want to update is in the users array to start with.
updateUser() {
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
const updatedUser = response.data.user;
// Find the index of the updated user in the users list
const index = this.users.findIndex(user => user.id === updatedUser.id);
// If the user was found in the users list update it
if (index >= 0) {
// Use vue set to update the array by index and force an update on the page
this.$set(this.users, index, updatedUser);
}
});
}
This could be a good starting point.
Unrelated Note:
You can add your mounted function code to its own method, for example
getUsers() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
then
mounted() {
this.getUsers()
}
this makes it a little cleaner and easier if you ever need to get the users again (example: if you start having filters the user can change)
As it could get more complex vuex would be a great addition.

Related

Load More Data On Scroll With Vue And Vuex

I would like to ask how can I display more data by using Vue and vuex. all data stored in vuex-store management already. From State management now I want to load more data on scrolling.
I found online solution by ajax. but I need to loading form state management (Vuex).
This is my Vue template:
<template>
<div>
<div class="panel panel-default">
<div class="panel-body">
<table class="table table-bordered table-striped">
<thead>
<tr>
<tr>
<th>Name - Number of Products: <span style="color: red"> {{products}} </span></th>
<th width="100"> </th>
</tr>
</tr>
</thead>
<tbody v-if="isLoaded">
<tr v-for="company, index in companies">
<td>{{ company.name }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return { }
},
computed: {
companies(){
return this.$store.getters['exa1Company/getProducts'];
},
products(){
return this.$store.getters['exa1Company/countProducts'];
}
},
mounted() {
this.$store.dispatch('exa1Company/indexResource');
}
}
</script>
My vuex store file is partial for simplicity
export const getters = {
countProducts(state) {
return state.list.data.length;
},
getProducts(state) {
return state.list.data;
},
getTodoById: (state) => (id) => {
return state.list.data.find(tod => tod.id === id)
}
};
export default {
namespaced: true,
state: customerState,
getters,
actions,
mutations,
};
something like this should work. use companiesLoaded in the template, and increase page when scrolled to bottom. I hope this helps.
data: function () {
return {
page: 1,
perPage: 20
}
},
computed: {
companies(){
return this.$store.getters['exa1Company/getProducts'];
},
companiesLoaded(){
return this.companies.slice(0, this.page * this.perPage)
},
...

[Vue warn]: Error in event handler for "update-user": "TypeError: Cannot set property 'name' of undefined"

I have 2 vue components one is a list users and the other is the details of a user.
What I'm trying to do is, if I have to update a user's details then I want it to automatically update.
The problem I'm getting is that I'm getting this error
[Vue warn]: Error in event handler for "update-user": "TypeError: Cannot set property 'name' of undefined"
Here is my code for the user details component
<template>
<div class="card">
<div class="card-header">
<span class="text-success" #click="updateUser">Save Changes</span>
</div>
<div class="card-body p-0">
<div class="card card-primary card-outline card-outline-tabs">
<div class="card-body">
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" class="form-control" id="name" v-model="name">
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['emit', 'user'],
data() {
return {
name: null,
}
},
mounted() {
},
methods: {
updateUser() {
axios.put('/admin/users/'+this.user.id, this.name).then(response => {
this.emit.$emit('update-user', {
user: this.name
});
});
}
}
}
</script>
and my vue component with the list of users
<template>
<div class="card">
<div class="card-body>
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="user in showUsers">
<td>{{user.name}}</td>
<td style="text-align: right;"><button class="btn btn-sm btn-outline-info" #click="detail(user)">Manage</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: ['emit', 'users'],
data() {
return {
perPage: 10,
currentPage: 1,
}
},
computed: {
filterUsers() {
return (this.userFilter === '') ? this.users : this.users.filter(u => { return u.name.toLowerCase().indexOf(this.userFilter.toLowerCase()) > -1; });
},
showUsers() {
let start = (this.currentPage - 1) * this.perPage;
let end = start + this.perPage;
return this.filterUsers.slice(start, end);
}
},
methods: {
detail(user) {
this.emit.$emit('manage-user', { user: user });
}
},
mounted(){
this.emit.$on('update-user', payload => {
this.user.name = payload.user.name;
});
}
}
</script>
The error you met is caused by this.user doesn't exists in the context inside mounted() { this.emit.$on('update-user', ....}.
As my understanding, you'd like to update the name of a specific user, but in that context, you only have all users (this.users) inside mounted(), you have to filter out that specific user then apply the update.
Below is one solution:
If user.id is unique, you can emit user.id and user.name from updateUser, then in mounted, find the specific user by user.id, then update it.
updateUser() {
axios.put('/admin/users/'+this.user.id, this.name).then(response => {
this.emit.$emit('update-user', {
name: this.name, // new user.name
id: this.user.id // assuming user.id is unique
});
});
}
this.emit.$on('update-user', payload => {
this.users.find(user => user.id===payload.id).name = payload.name; // find the user then update its name
});

After Vue.delete or $delete UI is not updating

this is my first question on stackoverflow.
So, I try to delete a item from array, I see, that in Vue Dev Tools it was deleted, but UI not updating.
I become this array as response from Laravel API and send dynamic to Vue Component like this
...
<admin-panel :jurisdictions="{{ $jurisdictions }}"></admin-panel>
...
then in my AdminComponent I redirect to AdminHomeComponent with props like this
<template>
<router-view :jurisdictions="jurisdictions"></router-view>
</template>
...
props: ['jurisdictions'],
...
created() {
this.$router.push({ name: "AdminHomeComponent" }).catch(err => {});
},
...
In AdminHomeComponent I have props too and router link to another component JurisdictionsComponent like this
<template>
...
<router-link :to="{name: 'JurisdictionsComponent'}"> Jurisdictions</router-link>
...
</template>
<script>
...
props: ["jurisdictions"]
...
</script>
And then will fun, wenn in JurisdictionsComponent I add a new one, or editing old one it works, there are reactive, but if I try to delete one, it still be reactive and I see this in Vue Dev Tools, but I cann't unterstand, why UI not updating..
JurisdictionsComponent
<template>
<div class="w-100">
<div id="jurisdictionsContainer" ref="jurisdictionsContainer">
<div class="panel-heading d-flex justify-content-between">
<h3 class="panel-title">Jurisdictions</h3>
<div class="pull-right">
<button #click.prevent="$modal.show('create-edit-jurisdiction', {'action' : 'create'})">
<i class="fas fa-plus-square"/> Create new
</button>
</div>
</div>
<table class="table table-hover mt-2 rounded" id="jurisdictions-table">
<thead class="thead-dark ">
<tr>
<th>Title</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="jurisdictions-table-body">
here I make v-for
<tr v-if="jurisdictions !== null" v-for="(jurisdiction, index) in this.jurisdictions" v-bind:key="jurisdiction.id"
class="result clickable-row"
#click="show($event, jurisdiction)">
<td class="title">
{{ jurisdiction.title }}
</td>
<td class="position-relative">
<button #click="$modal.show('create-edit-jurisdiction', {'jurisdiction': jurisdiction, 'index': index, 'action' : 'edit'})">
<div class="not-clickable">Edit</div>
here a show a delete modal window, use can deside delete or not, my code from ModalDeleteComponent see below
</button>
<button #click="$modal.show('delete-jurisdiction', {'jurisdiction': jurisdiction, 'index': index})">
<div class="not-clickable">Delete</div>
<i class="fas fa-trash-alt not-clickable"/>
</button>
</tr>
</tbody>
<delete-jurisdiction #onDeleted="onClickDelete"/>
<create-edit-jurisdiction #create="onClickCreate" #edit="onClickEdit":errors="this.errors.createEdit"/>
</div>
</div>
</template>
<script>
export default {
name: "JurisdictionsComponent",
props: ["jurisdictions"],
data() {
return {
isAllSelected: false,
errors: {
createEdit: {},
addEvent: {}
},
}
},
methods: {
/**
* Create a new jurisdiction
*
* #param data form
*/
onClickCreate(data) {
axios.post("/admin-dashboard/jurisdictions", data.form)
.then(response => {
response.data.image === undefined ? response.data.image = null : response.data.image;
response.data.selected = false;
this.jurisdictions.push(response.data);
this.$modal.hide("create-edit-jurisdiction");
this.errors.createEdit = {}
})
.catch(errors => {
this.errors.createEdit = errors.response.data.errors;
});
Here a try to delete jurisdiction, it deletes from database, from props in Vue Dev Tools but not from UI
/**
* Delete jurisdiction request
*
* #param index
*/
onClickDelete(index) {
axios.delete("/admin-dashboard/jurisdictions/" + this.jurisdictions[index].id)
.then(() => {
this.$delete(this.jurisdictions, index);
this.$modal.hide("delete-jurisdiction");
})
.catch(errors => {
console.log(errors)
});
},
/**
* Edit a jurisdiction
*
* #param data form
*/
onClickEdit(data) {
axios.patch(this.jurisdictions[data.index].path, data.form)
.then(response => {
this.$set(this.jurisdictions, data.index, response.data);
this.$modal.hide("create-edit-jurisdiction");
this.errors.createEdit = {}
})
.catch(errors => {
this.errors.createEdit = errors.response.data.errors;
})
},
}
</script>
ModalDeleteComponent
<template>
<modal name="delete-jurisdiction" #before-open="beforeOpen" height="200" #before-close="beforeClose">
<div class="h-100">
<div v-if="jurisdiction !== null" class="p-4 mt-2">
<h3>Do you want really delete
<a :href="'/admin-dashboard/jurisdictions/'+jurisdiction.id"><strong>{{ jurisdiction.title }}</strong></a>
<span v-if="jurisdiction.events.length > 0">
with {{ jurisdiction.events.length }} {{ jurisdiction.events.length === 1 ? 'event' : "events"}}
</span>?
</h3>
</div>
<div class="bg-dark d-flex justify-content-around p-2 position-absolute w-100" style="bottom: 0">
<button class="btn btn-danger" #click="submitDelete">Delete</button>
<button class="btn btn-secondary" #click="$modal.hide('delete-jurisdiction')">Cancel</button>
</div>
</div>
</modal>
</template>
<script>
export default {
name: "ModalDeleteJurisdictionComponent",
data() {
return {
jurisdiction: null,
index: ""
}
},
methods: {
submitDelete() {
this.$emit('onDeleted', this.index);
},
beforeOpen (event) {
this.jurisdiction = event.params.jurisdiction;
this.index = event.params.index;
},
beforeClose(event) {
this.jurisdiction = null;
this.index = "";
}
}
}
</script>
I know, my question is too long, but if anyone tries to answer this, I will very happy))
I'm open to any contra questions. Sorry for my English
So, thanks oshell for a tipp. Ich have renamed in jurisdictions to dataJurisdictions and init in created() {this.dataJurisdictions = this.jurisdictions} as well. First of all I want to avoid duplication of data in components and work only with props, but nevertheless it works. Thanks a lot!
You are adding to jurisdictions, which is a prop.
this.jurisdictions.push(response.data);
However, you should either update the prop in the parent component, to trigger a prop change and re-render or assign the prop to the components data as initial value and then update data.
Changing prop in parent component can be done using $emit or by using Vuex.
Assigning data locally just needs a different value name.
this.localJurisdictions = this.jurisdictions
And for updating then use this new data value. (Use accordingly in template.)
this.localJurisdictions.push(response.data);

I cannot display data form an API using v-for in vuejs

I cannot get or display data from an API but the API is working fine when I console.log the issue. but I cannot display data in my table I used v-for I am new to Vue.js I don't know how to solve this issue
I am using Vue.js 2, Axios method to connect to my API
here's my code
import axios from 'axios'
export default{
data(){
return{
errorMessage: "",
successMessage: "",
users: []
}
},
mounted: function(){
this.getAllUsers();
},
methods:{
getAllUsers: function(){
axios.get('http://localhost:8888/vue-and-php/public/api/config.php?action=read', { crossdomain: true })
.then(function(response){
//console.log(response);
if(response.data.error){
app.errorMessage = response.data.message;
}else{
app.users = response.data.users;
}
});
}
}
}
THIS IS WHERE I WANT TO DISPLAY MY DATA
<!--test PHP-->
<button class="fright addNew btn-sm btn-success" data-toggle="modal" data-target="#exampleModalLong">Add New</button>
<p class="errorMessage alert alert-danger" v-if="errorMessage">
{{errorMessage}}
</p>
<p class="successMessage alert alert-success" v-if="successMessage">
{{successMessage}}
</p>
<table class="table">
<tr>
<th>ID</th>
<th>User Name</th>
<th>Name</th>
<th>Email</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<tr v-for="user in users">
<td>{{user.id}}</td>
<td>{{user.username}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td><button class="btn btn-sm btn-success">ADD</button></td>
<td><button class="btn btn-sm btn-danger">DELETE</button></td>
</tr>
</table>
You cannot do
app.users = response.data.users;
Since app is not defined anywhere and your browsers console must be throwing an error, what are trying to do over here is assigning the users you have defined in data function.
That users object is present in current context and can be accessed through 'this' keyword.
Try :
if(response.data.error){
this.errorMessage = response.data.message;
}
else{
this.users = response.data.users;
}
Thank you guys for your time I used your code but with little corrections
this is the final code that works
getAllUsers: function(){
axios.get('http://localhost:8888/vue-and-php/public/api/config.php?action=read', { crossdomain: true })
.then((response) => {
//console.log(response);
if(response.data.error){
this.errorMessage = response.data.message;
}else{
this.users = response.data.users;
}
});
}
You probably want to set some information into your users data field. You need to do something like:
if (response.data.error) {
this.errorMessage = response.data.message;
} else {
this.users = response.data.users;
}
Explanation: Vue reacts to changes in data and props. By doing this.users = response.data.users, you are telling Vue that the users have changed and then Vue will re-render based on how you've used users inside your template.

Vue.js/Axios - Duplicate results in list. Has unique-keys in v-for

I have two other uses of v-for in separate components. They also sometimes throw errors. All three v-for invocations are wrapped with v-if/else. Here is the code that produces duplicate key errors & renders data twice:
AccountDashboard.vue
<tbody>
<tr v-if="!residents.length" class="table-info">
<td class="text-center">
<p>
No residents on record.
</p>
</td>
</tr>
<template v-else>
<tr is="AccountResidentList"
v-for="resident in residents"
v-bind:key="'resident-list-' + resident.id"
v-bind:first_name="resident.first_name"
v-bind:last_name="resident.last_name"
v-bind:dob="resident.dob | date_formatted"
>
</tr>
</template>
</tbody>
Note the unique id attempt in the binding of key.
Here is a look at the child component
ProviderAccountList.vue
<template>
<tr class="AccountResidentList">
<td>
{{ this.$attrs.id }}
</td>
<td>
{{ this.$attrs.first_name }} {{ this.$attrs.last_name }}
</td>
<td>
{{ this.$attrs.dob }}
</td>
<td>
<button #click="toResidentProfile({account_id, id})" class="btn btn-sm btn-purple btn-with-icon">
<div class="ht-25">
<span class="icon wd-25"><i class="fa fa-eye"></i></span>
<span class="pd-x-10">view</span>
</div>
</button>
</td>
<!--TODO: Add view profile button-->
</tr>
</template>
<script>
import Axios from "axios";
import router from "../../router";
import { mapGetters } from "vuex";
import moment from "moment";
export default {
name: "AccountResidentList",
computed: {
...mapGetters['Resident', {
resident: 'getResident'
}]
},
filters: {
date_formatted: (date) => {
return moment(date).format('MMMM Do, YYYY');
}
},
methods: {
toResidentProfile(account_id, resident_id) {
router.push(`/accounts/${account_id}/residents/${resident_id}`)
}
},
};
</script>
<style scoped></style>
My Axios call looks like:
Account.js (a namespaced vuex-module)
async retrieveAccount(context, account_id) {
// Axios.defaults.headers.common['Authorization'] = 'Bearer ' + window.$cookies.get('jwt')
let response
let valid_id = window.$cookies.get('valid_id');
response = await Axios.get(`http://localhost:3000/api/v1/providers/${valid_id}/accounts/${account_id}`, { headers: { 'Authorization': 'Bearer ' + window.$cookies.get('jwt') } })
.then((response) => {
let account = response.data.locals.account;
let account_address = response.data.locals.account_address;
let residents = response.data.locals.residents;
// set Account
context.dispatch('Account/setId', account.id, {root: true});
context.dispatch('Account/setProviderId', account.provider_id, {root: true});
.
.
.
// set AccountAddress
// !Array.isArray(array) || !array.length
if (account.address) {
context.dispatch('Account/setAddressId', account_address.id, {root: true});
context.dispatch('Address/setId', account_address.id, {root: true});
.
.
.
// set AccountResidents
// !Array.isArray(array) || !array.length
residents.forEach(resident => {
if (resident) {
// Add object to parent's list
context.dispatch('Account/setResidents', resident, {root: true}); // Set attr values for object
context.dispatch('Resident/setId', resident.id, {root: true});
.
.
.
(remaining attrs removed for brevity)
}
})
router.push(`/providers/${account.provider_id}/accounts/${account_id}`);
})
.catch(function(error) {
console.log(error);
})
Note: the Account action #setResidents simply calls the mutator that adds one resident to a list total.
i.e state.list.push(resident)
I logged the response to the console and can confirm that the data isn't being sent twice (or more) from my Axios call.
I have reviewed & attempted the following to no avail:
https://alligator.io/vuejs/iterating-v-for/
https://www.reddit.com/r/vuejs/comments/7n3zi4/vue_warn_duplicate_keys_detected_vfor_with/
https://github.com/hejianxian/vddl/issues/23
https://github.com/hejianxian/vddl#warning
https://medium.com/#chiatsai/vue-js-common-issue-duplicate-keys-stops-components-rendering-df415f31838e
Finally, It should be mentioned that I have tried variations of using/not using template to wrap the list, including/not including the for loop in the template, etc..
Did not anticipate it would be this bothersome to iterate a collection.
Am I overlooking something obvious?
Update: What worked for me
I needed access to the resident.id also the id declared in the paren seems like an index. So here is a look at what removed the duplicate render errors and allow me access to the resident's id even after fixing the duplicate keys error:
<template v-else>
<tr is="AccountResidentList"
v-for="(resident, id) in residents"
v-bind:key="id"
v-bind:id="resident.id"
v-bind:first_name="resident.first_name"
v-bind:last_name="resident.last_name"
v-bind:dob="resident.dob | date_formatted"
>
</tr>
</template>
Thanks again #Billal Begueradj for the assist!
For me, I suspect that in residents there are entries which have the same id. So we have to find out a way to overcome this issue. We can give it an efficient try as follows:
<tr
is="AccountResidentList"
v-for="(resident, id) in residents"
:key="id"
// rest of your code