Update data after change without page refreshing using vue.js - vue.js

I wrote such code:
<template>
<div class="home">
<HelloWorld tableTitle="Goods:">
<table class="table table-striped">
<tbody>
<tr v-for="(i, index) in items.data" :key="index">
<td>{{ i.id }}</td>
<td>{{ i.name }}</td>
<td>{{ i.producer }}</td>
<td><font-awesome-icon v-if="i.received" icon="check" /><font-awesome-icon v-else icon="times" /><td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import HelloWorld from "#/components/HelloWorld.vue";
import axios from "axios";
export default {
name: "home",
components: {
HelloWorld
},
data: () => ({
items: {},
errors: []
}),
beforeMount() {
axios
.get("http://40.115.119.247/AgileSelection/tnt/status")
.then(response => {
this.items = response.data;
console.log(this.items);
})
.catch(e => {
this.error.push(e);
});
}
};
<script>
Now, for refreshing information I just use the refresh button.
What and where should I add some lines of code to update information but without refreshing the page? Because, I am updating data every 5 seconds. So I think that manually updating is not so good idea.

do something like this
// data property
data () {
return {
...
interval: null
}
},
// in your created hook
created () {
this.interval = setInterval(this.refreshData, 5000)
},
beforeDestroy () {
clearInterval(this.interval)
},
// in methods property
methods: {
refreshData () {
// fetch data
axios.get("http://40.115.119.247/AgileSelection/tnt/status")
.then(response => {
this.items = response.data
})
}
}
this will fetch your data from your API and update the list automatically. this will update your UI as well.

you can try using location.reload() after the code where you register the update
for example
handleSubmit() {
this.registerServices(this.organization)
location.reload();
}

Related

Vue.js Displaying data from websocket into a table and sending data to other component

Version: Vue CLI 2.6.x
I am trying to resolve two issues:
Issue 1:
My Vue app has subscribed to updates via a websocket. I am getting the data continuously and need to keep the table updated with the received data. However the table remains empty even when the list (aqiDataList) has content in it.
Issue 2:
I also need to pass the aqiDataList to the AQITableComponent (where the actual table was originally suppose to be) but having this same issue
App.vue
<template>
<v-container>
<AQITableComponent :aqiList="aqiDataList" />
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">
Name
</th>
<th class="text-left">
Age
</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in aqiDataList"
:key="item.id"
>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.location }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-container>
</template>
<script>
import AQITableComponent from './AQITableComponent';
export default {
name: 'AQIComponent',
components: {
AQITableComponent
},
data: function () {
return {
connection: null,
aqiDataList: []
};
},
mounted() {
},
methods: {
},
created: function () {
console.log("Starting connection to WebSocket Server");
this.connection = new WebSocket("wss://my-websocket.com");
this.connection.onmessage = function (event) {
//console.log(event.data);
let aqiDataString = event.data;
this.aqiDataList = [];
let parsedData = JSON.parse(aqiDataString);
parsedData.forEach((aqiData) => {
this.aqiDataList.push({
... object
});
});
console.log(this.aqiDataList);
};
this.connection.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the echo websocket server...");
};
},
}
</script>
AQITableComponent.vue
<template>
<v-container>
<v-simple-table>
<template v-slot:default>
.. same table as shown above
</template>
</v-simple-table>
</v-container>
</template>
<script>
export default {
name: 'AQITableComponent',
props: ['aqiList'],
data: function () {
},
}
</script>
(1) Try using the arrow function for the onmessage event:
from: this.connection.onmessage = function (event) {...}
to: this.connection.onmessage = (event) => {...}
or: this.connection.addEventListener("message", (event) => {...});
This way the this.aqiDataList will be available on your component. Inside the event callback.
This should also solve the problem (2) since your array is not being updated on the first place.

After making a POST request to my heroku endpoint and saved it to my mongoDB, the data only shows if I refresh the page

I deployed my Vue app to Netlify and the backend to heroku. Everything works fine, I can edit, delete and get data from my database, except when I submit the form (creating a new client) and I redirect to this.$router.push("/tabela"); . The data is created, but when I go to the ListComponent.vue (path:'tabela') my data isn't there. It only shows when I refresh the page. Before deploying to heroku, I fixed the issue with window.location.href="/tabela"; instead this.$router.push but now, If I use window.location.href="/tabela" I cannot save to my database anymore. I need to use this.$router.push in order to make it "work" but as I said, then I need to refresh the page to update my table with the new client.
Here is my app https://cadastro-app.netlify.app/ .
CreateComponent.vue
methods: {
submitForm(){
if(this.cliente.cpf === ''){
this.cliente.cpf = 'Não Informado'
}else if(this.cliente.cnpj === ''){
this.cliente.cnpj = 'Não Informado'
}
axios.post('https://cadastro-backend-app.herokuapp.com/clientes', {
data: this.cliente
}).then(function(){
this.cliente = {
nome: '',
sobrenome: '',
email: '',
telefone: '',
cnpj: '',
cpf: ''
}
}).catch(function (error) {
console.log(error);
});
/* window.location.href="/tabela"; */
this.$router.push("/tabela");
}
}
ListComponent.vue
<template>
<div class="row">
<div class="col-md-12 ">
<div class="search">
<input #keyup.enter.prevent="search()" v-model='nomePesquisado' class="form-control mb-3" type="text" placeholder="Pesquisar" aria-label="Search"/>
</div>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Nome completo</th>
<th>Email</th>
<th>telefone</th>
<th>CNPJ</th>
<th>CPF</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="cliente in clientes" :key="cliente._id">
<td>{{ cliente.nome }}</td>
<td>{{ cliente.email }}</td>
<td>{{ cliente.telefone }}</td>
<td>{{ cliente.cnpj }}</td>
<td>{{ cliente.cpf }}</td>
<td>
<router-link :to="{name: 'edit', params: { id: cliente._id }}" class="btn btn-success">Editar
</router-link>
<button #click.prevent="removerCliente(cliente._id)" class="btn btn-danger">Remover</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
clientes: [],
nomePesquisado:''
}
},
created() {
let apiURL = 'https://cadastro-backend-app.herokuapp.com/clientes/';
axios.get(apiURL).then(res => {
this.clientes = res.data;
console.log(this.clientes);
}).catch(error => {
console.log(error)
});
},
methods: {
removerCliente(id){
const apiURL = `https://cadastro-backend-app.herokuapp.com/clientes/${id}`;
const indexOfArrayItem = this.clientes.findIndex(i => i._id === id);
if (window.confirm("Tem certeza que deseja remover este item?")) {
axios.delete(apiURL).then(() => {
this.clientes.splice(indexOfArrayItem, 1);
}).catch(error => {
console.log(error)
});
}
},
search(){
this.$router.push(`results/${this.nomePesquisado}`);
}
}
}
</script>
router index.js
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "home",
component: () => import("../components/CreateComponent")
},
{
path: "/tabela",
name: "tabela",
component: () => import("../components/ListComponent")
},
{
path: "/edit/:id",
name: "edit",
component: () => import("../components/EditComponent")
},
{
path: "/results/:id",
name: "results",
component: () => import("../components/Results")
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
You are changing the route (this.$router.push("/tabela");) too early
This is what is happening:
You make a POST request
Without waiting for a request to complete, you are telling Vue router to switch to ListComponent (this.$router.push("/tabela");)
Router activates ListComponent component
ListComponent runs a GET request to the server in it's created hook
Result is a "race". Will POST request be fast enough so the GET request sees the new data ?
To be sure, move this.$router.push("/tabela"); inside then

Error in Vuex Computed property was assigned to but it has no setter

I am trying to display a list of Invites to groups.
Sometimes this component displays as expected, and sometimes not at all. This error returns:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Computed property "groupInvites" was assigned to but it has no setter.
found in
---> <InviteList> at src/components/Invites/InviteList.vue
<UserProfile> at src/views/UserProfile.vue
<App> at src/components/App.vue
<Root>
Here is the it component generating the error:
<template>
<div class="">
<h4 mt-10 class="text-warning">Your Current Invites:</h4>
<table class="table">
<thead>
<tr>
<th>Group Name</th>
<th>Invited By</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(invite, i) in groupInvites" :key="`${i}-${invite.id} `">
<td>
<router-link :to="`/groups/${invite.group_id}`" class="lightbox">
{{ invite.group_name }}
</router-link>
</td>
<td>{{ invite.sender_id }}</td>
<td>{{ moment(invite.created_at).strftime("%A, %d %b %Y %l:%M %p") }}</td>
<td scope="row">
<a class="btn btn-success mr-3" #click="acceptInvite(invite)">
Join Group
</a>
<a flat color="grey" #click="deleteInvite(invite.id)">
<i class="fa fa-trash " ></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import moment from 'moment-strftime';
import InvitesService from '../../services/InvitesService';
import UsersService from '../../services/UsersService';
export default {
name: "InviteList",
components: {
// NewInvite
},
props: {
// user: {
// type: Object,
// required: true
// },
},
computed: {
user() {
return this.$store.state.auth.user
},
groupInvites() {
return this.$store.state.invites.groupInvites;
}
},
mounted() {
this.getInvites();
},
methods: {
async getInvites () {
console.log('in invitelist, getting invites for user: ', this.user.id)
this.groupInvites = await this.$store.dispatch("getGroupInvites", this.user);
},
async getUser (id) {
this.sender = await UsersService.getUserById({
id: id
});
},
deleteInvite: async function (id) {
if(confirm("Do you really want to reject this invite?")) {
let response = await InvitesService.deleteInvite(id)
if (response.status === 200) {
console.log('In Invite.vue, invite deleted, about to emit');
this.$emit('invite-deleted');
}
}
this.getInvites();
},
async acceptInvite(invite) {
let result = await InvitesService.acceptInvite(invite.invitation_code)
this.$emit("membership-created");
console.log('this is the membership created: ', result.data)
// window.analytics.track('Group Joined', {
// title: this.group.name,
// user: this.$store.getters.user.username
// });
this.getInvites();
},
moment: function (datetime) {
return moment(datetime);
}
}
};
</script>
Separately, here is the store module:
import InvitesService from '#/services/InvitesService'
export const state = {
groupInvites: []
}
export const mutations = {
setGroupInvites(state, groupInvites) {
state.groupInvites = groupInvites;
}
}
export const actions = {
getGroupInvites({ commit }, user) {
console.log('in store. getting invites, for user: ', user.id)
InvitesService.getAllUserInvitation(user.id)
.then(resp => {
console.log('in store, getGroupInvites,this is groupInvites: ', resp.data);
commit('setGroupInvites', resp.data);
});
}
}
export const getters = {
}
Incidentally, getGroupInvites is being called twice. here are the console.logs:
in invitelist, getting invites for user: 9
invites.js?d00b:16 in store. getting invites, for user: 9
InvitesService.js?109c:10 in service getting all user invites for user: 9
invites.js?d00b:16 in store. getting invites, for user: undefined
InvitesService.js?109c:10 in service getting all user invites for user: undefined
notice user is undefined on the second go around.
It is possible to assign a value to a computed if you've defined it using a computed setter, but you haven't and probably don't need to. So this line is wrong and throws the error because it tries to do that:
this.groupInvites = await this.$store.dispatch("getGroupInvites", this.user);
But it's ok because this.groupInvites already gets its value reactively from the same state that getGroupInvites action populates anyway (state.groupInvites) so it's also unnecessary. Change that line to:
this.$store.dispatch("getGroupInvites", this.user);
and allow the computed to update itself.

VueJS method returns promise. How to display data in template

VueJS front uses axios to retrieve data from Express API. Data returns as promise. I can't figure out how to display it in the template. The goal is to add this data to be displayed within a leaderboard, so I am doing a first query to get the username and the score(which is a sum) and then a second to get the data on the last single activity by each user.
Here is the code.
Leaderboard component:
<template>
<table class="ui celled table">
<thead>
<tr><th colspan="4">Leaderboard for {{ currentGroup.name }}</th></tr>
<tr>
<th>Username</th>
<th>Last Action</th>
<th>Date</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr v-for="(leader) in leaderboard" :key="leader.users_id">
<td>
<h4 class="ui image header">
<img v-if="leader.avatar_url" :src="leader.avatar_url" class="ui mini rounded image">
<v-gravatar v-else :email="leader.email" class="ui mini rounded image" />
<div class="content">
{{leader.username}}
</div>
</h4>
</td>
<td>{{ lastActByUser(leader.users_id).deed }}</td>
<td></td>
<!-- <td>{{ lastAct(leader.id).deed }}</td>
<td>{{ moment(lastAct(leader.id).created_at).strftime("%A, %d %b %Y %l:%M %p") }}</td> -->
<td>{{leader.count}}</td>
</tr>
</tbody>
</table>
</template>
<script>
import moment from 'moment-strftime'
import _ from 'lodash'
import ReportsService from '#/services/ReportsService'
import ActsService from '#/services/ActsService'
export default {
name: "Leaderboard",
data() {
return {
}
},
computed: {
leaderboard () {
return this.$store.getters.leaderboard;
},
currentGroup () {
return this.$store.getters.currentGroup;
}
// ,
// lastAct (userId) {
// return _.orderBy(this.actsByUser(userId), 'created_at')[0];
// }
},
mounted () {
this.getLeaderboard();
},
methods: {
getLeaderboard: async function () {
console.log('currentGroup: ', this.$store.getters.currentGroup)
this.$store.dispatch("updateLeaderboard", this.$store.getters.currentGroup);
},
moment: function (datetime) {
return moment(datetime);
}
,
async lastActByUser (leader_id) {
console.log('getting last act for user')
const response = await ActsService.fetchLastActByUser ({
userId: leader_id
});
this.deed = response.data.deed
this.created_at = response.data.created_at
console.log('lastAct response: ', response.data)
}
}
};
</script>
here is ActsService:
import Api from '#/services/Api'
export default {
fetchActs () {
return Api().get('acts')
},
...
fetchLastActByUser (params) {
console.log('calling the last act service with userId: ', params.userId)
return Api().post('acts/last_by_user', params)
},
}
here is the Express route:
const express = require('express');
const User = require('../models/User');
const auth = require('../middlewares/authenticate');
const Act = require('../models/Act');
let router = express.Router();
router.get('/', async (req, res) => {
const acts = await Act
.query().eager('user');
res.json(acts);
});
...
// GET last act for specified user
router.post('/last_by_user', async (req, res, next) => {
const lastAct = await Act
.query()
.findOne({
users_id: req.body.userId
});
console.log('lastAct on server side is: ', lastAct)
res.json(lastAct);
})
module.exports = router;

Vuetify Using datatable with external data from an API with Vuex

I want to use the vuetify framework with Vuex , but there is limited documentation about using it with Vuex.
I want to:
Get data from an external API ( but only the data needed )
Then Save the data in state and edit or whatever
Then push any changes back to the api
I have tried some of the external pagination and sorting examples with vuetify , but I can't get it to show all record count unless I hard code it.
I am quite new to Vue and Vuetify , so maybe I am misunderstanding something.
<template>
<div>
<v-data-table
:headers='headers'
:items='items'
:length='pages'
:search='search'
:pagination.sync='pagination'
:total-items='totalItemCount'
class='elevation-1'
>
<template slot='items' slot-scope='props'>
<td class='text-xs-right'>{{ props.item.id }}</td>
<td class='text-xs-right'>{{ props.item.first_name }}</td>
<td class='text-xs-right'>{{ props.item.last_name }}</td>
<td class='text-xs-right'>{{ props.item.avatar }}</td>
</template>
</v-data-table>
</div>
</template>
<script>
import moment from 'moment'
import axios from 'axios'
export default {
name: 'test-table',
watch: {
pagination: {
async handler () {
const rowsPerPage = this.pagination.rowsPerPage
// const skip = (this.pagination.page - 1) * rowsPerPage
const pageNumber = this.pagination.page
const res = await axios.get(`https://reqres.in/api/users?page=${pageNumber}&per_page=${rowsPerPage}`)
this.items = res.data.data
this.$store.commit('saveTableData', this.items)
},
deep: true
}
},
computed: {
pages () {
return 171
},
totalItemCount () {
return 400
}
},
async mounted () {
const rowsPerPage = this.pagination.rowsPerPage
const skip = (this.pagination.page - 1) * rowsPerPage
const res = await axios.get(`https://reqres.in/api/users?page=${skip}&per_page=${rowsPerPage}`)
this.items = res.data.data
this.$store.commit('saveTableData', this.items)
},
methods: {
nzDate: function (dt) {
return moment(dt).format('DD/MM/YYYY')
}
},
data: () => ({
search: '',
// totalItems: 0,
items: [],
pagination: {
sortBy: 'Date'
},
headers: [
{ text: 'ID', value: 'id' },
{ text: 'First Name', value: 'first_name' },
{ text: 'Last Name', value: 'last_name' },
{ text: 'Avatar', value: 'avatar' }
]
})
}
This is my working setup:
<template>
<v-data-table
:total-items="pagination.totalItems"
:pagination.sync="pagination"
:items="rows"
:headers="columns">
<template slot="headers" slot-scope="props">
<tr :active="props.selected">
<th v-for="column in props.headers">
{{ column.value }}
</th>
</tr>
</template>
<template slot="items" slot-scope="props">
<tr>
<td v-for="cell in props.item.row">
<v-edit-dialog lazy>
{{ cell.value }}
<v-text-field
:value="cell.value"
single-line
counter>
</v-text-field>
</v-edit-dialog>
</td>
</tr>
</template>
</v-data-table>
</template>
<script>
export default {
data: () => ({
pagination: {
page: 1,
rowsPerPage: 10,
totalItems: 0
},
selected: []
}),
computed: {
columns: {
get () {
return this.$store.state.columns
}
},
rows: {
get () {
return this.$store.state.rows
}
}
},
methods: {
async getRowsHandler () {
try {
const {total} = await this.$store.dispatch('getRows', {
tableIdentifier: this.$route.params.tableIdentifier,
page: this.pagination.page,
size: this.pagination.rowsPerPage
})
this.pagination.totalItems = total
} catch (error) {
// Error
}
}
}
}
</script>
I didn't implement everything. If you miss a specific part ask again and I will update my example. One more tip: You should avoid watch deep wherever possible. It can result in heavy calculations.
Assuming this is Vuetify v1.5, the documentation on the total-items prop on data-tables states:
Caution: Binding this to a blank string or using in conjunction with
search will yield unexpected behaviours
If you remove the 'search' prop from your table the record count will show again. If you're doing external stuff anyway, you'll won't want the default search functionality.