Vue - axios - handling same requests - vue.js

In my project I've got such structure:
Client page which has sidebar, general client's info and router-view for children views.
routes:
{ path: '/clients/:id', component: Client,
children: [
{
path: '/',
component: ClientReview,
name: 'client-review'
},
{
path: 'balances',
component: ClientBalances,
name: 'client-balances'
},
{
path: 'report',
component: MainReport,
name: 'client-report'
},
Client's component (Client.vue):
<template>
<el-row>
<client-menu></client-menu>
<el-col class="client-view" :md="{ span: 22, offset: 2}" :sm="{ span: 20, offset: 4}" :xs="{ span: 18, offset: 6}">
<client-bar></client-bar>
<transition name="el-zoom-in-center">
<router-view></router-view>
</transition>
</el-col>
</el-row>
</template>
<script>
import ClientMenu from './ClientMenu.vue'
import ClientBar from './ClientBar.vue'
export default {
data () {
return {
loading: false,
};
},
components: {
'client-menu': ClientMenu,
'client-bar': ClientBar,
}
}
</script>
ClientBar component (ClientBar.vue):
<template>
<div class="client-bar">
<el-col :span="18">
<h3>{{ client.Name }}</h3>
<h4>{{ client.Address }}</h4>
</el-col>
<el-col :span="6" style="text-align: right;">
<el-button-group>
<el-button icon="edit" size="small"></el-button>
<el-button icon="share" size="small"></el-button>
</el-button-group>
</el-col>
<div class="clrfx"></div>
</div>
</template>
<script>
export default {
data () {
return {
client: {}
}
},
mounted () {
this.loadClient()
},
methods: {
loadClient: function() {
self = this;
this.axios.get('http://127.0.0.1:8020/clients/'+self.$route.params.id)
.then(function(response) {
self.client = response.data;
self.loading = false;
})
.catch(function(error) {
console.log(error);
});
}
}
}
</script>
And I've got ClientReview component, which is root component for clients/:id route and use the same api to load clients information as ClientBar:
<template>
<div>
<el-row v-loading.body="loading">
<el-col :span="12">
<table class="el-table striped">
<tr>
<td class="cell">Полное наименование</td>
<td class="cell">{{ clientInfo.FullName }}</td>
</tr>
<tr>
<td class="cell">УНП</td>
<td class="cell">{{ clientInfo.UNP }}</td>
</tr>
<tr>
<td class="cell">ОКЭД</td>
<td class="cell">{{ clientInfo.Branch.code }}<br>{{ clientInfo.Branch.name }}</td>
</tr>
<tr>
<td class="cell">Адрес</td>
<td class="cell">{{ clientInfo.Address }}</td>
</tr>
<tr>
<td class="cell">Аналитик</td>
<td class="cell">{{ clientInfo.Analytic.first_name }} {{ clientInfo.Analytic.last_name }}</td>
</tr>
<tr>
<td class="cell">Менеджер</td>
<td class="cell">{{ clientInfo.Manager.first_name }} {{ clientInfo.Manager.last_name }}</td>
</tr>
</table>
</el-col>
<el-col :span="12">
<classification-report></classification-report>
</el-col>
</el-row>
</div>
</template>
<script>
import ClassificationReport from '../reports/ClassificationReport.vue'
export default {
data () {
return {
loading: false,
clientInfo: {}
}
},
created () {
this.Client();
},
methods: {
Client: function() {
self = this;
self.loading = true;
self.axios.get('http://127.0.0.1:8020/clients/'+self.$route.params.id)
.then(function(response) {
self.clientInfo = response.data;
self.loading = false;
})
.catch(function(error) {
console.log(error);
});
}
},
components: {
'classification-report': ClassificationReport
}
}
</script>
The problem is when I load page client/:id first time or refresh the page client's data in ClientReview doesn't load.
The component is rendered (as I see it in Vue Devtools), and both requests to server are sent, but clientInfo object in ClientReview still empty.
Than if I go to balances or report page and after that go to client-review page everything is loaded.
Hope someone could help me.

It's because self happens to be another reference to the window object, and is available in all modules.
Let's walk thru the steps and see why this bug is happening.
You load up client:/id.
The created method in ClientReview does ajax request, and assigns self to this.
The mounted method in ClientBar does ajax request, and reassigns self to this. Note that this also changed the self variable reference in ClientReview.
The ClientReview ajax finishes and assigns self.clientInfo = response.data;. Since self is a reference to ClientBar, and ClientBar does not declare clientInfo as a root data property, this assignment does nothing.
ClientBar ajax finishes and assigns self.client = response.data;, which works.
You route away from ClientReview.
You route back to ClientReview. Repeat step 2.
ClientReview successfully renders because ClientBar has already rendered, and does not reassign self.
The answer is to do let self = this instead of self = this.
The lesson is ALWAYS DECLARE YOUR VARIABLES with var, let or const.

Related

Strange behavior in Vue 3 without any error or warning

I have the following Vue 3 component that I am using in 2 pages that are setup like this in vue.config.js:
module.exports = {
// Put this in the ASP.NET Core directory
outputDir: "../wwwroot/app",
pages: {
vehicleDetails: "src/VehicleDetails.js",
driverDetails: "src/DriverDetails.js"
}
};
Both of these pages work and share the same underlying 'Certificates' component that looks like this:
<template>
<div>
<h3 id="Vue_Certificates">Certificates</h3>
<div>
objectId: {{objectId}} test
<table class="table table-striped table-hover table-condensed2" style="clear: both;">
<thead>
<tr>
<th><b>test</b></th>
<th style="text-align: right;">
<a href="#" #click="addCertificate">
<i class="fa fa-plus-square"></i> Add
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="certificate in certificates" v-bind:key="certificate">
<td>{{ certificate.CertificateTypeId }}</td>
<td>
<a href="#" #click="removeCertificate(index)" title="Delete" style="float: right;" class="btn btn-default">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { onMounted, ref } from "vue";
import $ from 'jquery';
import _ from 'lodash';
export default {
components: {
},
props: {
objectId: String,
ownerIsVehicle: Boolean
},
data() {
return {
certificates: [],
types: []
}
},
created() {
const requestOne = axios.get("/api/v1.0/CertificateType/GetCertificateTypes");
const requestTwo = axios.get("/api/v1.0/Certificate/GetCertificateByObjectId", { params: { objectId: this.objectId, ownerIsVehicle: this.ownerIsVehicle } });
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0];
const responseTwo = responses[1];
alert("Axios calls completed 1");
this.types = responseOne.data;
var mappedData = responseTwo.data.map(x => ({
...x,
ValidFrom: new Date(x.ValidFrom),
ValidTill: new Date(x.ValidTill)
}));
this.certificates = mappedData;
alert("Axios calls completed 2");
console.log("succes");
})).catch(errors => {
console.log("fail");
});
},
methods: {
removeCertificate(index) {
this.certificates.splice(index, 1);
},
addCertificate() {
alert("Hello?");
}
}
}
</script>
The above code does NOT render the Vue component, but once I take out the two cells containing the anchors that call addCertificate & removeCertificate it all suddenly works.
For some reason I don't get any error in Chrome dev tools .. npm console .. visual studio console .. nowhere, so I have no clue at all what is going wrong.
Important: If I take out 1 of the 2 lines in vue.config.js it works on the page that is still added. The purpose of this component is that it can be reused.
FYI: I shrank down the code as much as possible to make sure other code bits aren't the cause.
For the record, the data property "certificates" was first a ref([]) but that doesn't seem to help at all.
Thanks in advance!

How to fix the warning "The data property is already declared as a prop. Use prop default value instead" in Vue JS

Company Profile
<dashboard-card
title="Company Profile"
titleColor="primary"
#click.native="componentClicked"
id="company-profile" class="vs-con-loading__container"
:isData= "isData"
>
<div class="w-full text-xl md:text-2xl font-bold">
{{ profile.companyName }}
</div>
<div class="md:text-md">
<div class="py-2">
{{ profile.description }}
</div>
<table class="table-auto border-collapse">
<tr>
<td class="py-1 pr-2 md:pr-4 font-bold">CEO:</td>
<td>{{ profile.ceo }}</td>
</tr>
<tr>
<td class="py-1 pr-2 md:pr-4 font-bold">Exchange:</td>
<td>{{ profile.exchange }}</td>
</tr>
<tr>
<td class="py-1 pr-2 md:pr-4 font-bold">Ticker:</td>
<td class="uppercase text-primary font-semibold">
{{ profile.symbol }}
</td>
</tr>
<tr>
<td class="py-1 pr-2 md:pr-4 font-bold">Industry:</td>
<td>{{ profile.industry }}</td>
</tr>
<tr>
<td class="py-1 pr-2 font-bold">Sector:</td>
<td>{{ profile.sector }}</td>
</tr>
</table>
<div class="pt-2">
<vs-icon
:icon="'icon-globe'"
icon-pack="feather"
class="text-primary pr-2"
></vs-icon>
<a class="text-primary" :href="profile.website" target="blank">
{{ profile.website }}
</a>
</div>
</div>
</dashboard-card>
</template>
<script>
import DashboardCard from "../dashboard-card";
export default {
name: "CompanyProfile",
extends: DashboardCard,
components: { DashboardCard },
props: {
ticker: {
type: String,
default: "",
},
},
data() {
return {
profile: {},
isData: "",
};
},
watch: {
ticker(value) {
this.isData = "";
this.getProfile(value);
},
},
mounted() {
this.isData = "";
this.getProfile(this.ticker);
},
methods: {
getProfile(ticker) {
this.$vs.loading({
container: "#company-profile",
type: "point",
scale: 0.8,
});
this.$api.ticker.profile(ticker).then((response) => {
if (Object.keys(response).length == 0) {
console.log("response is empty");
this.isData = "404";
this.$vs.loading.close("#company-profile > .con-vs-loading");
return;
}
this.profile = response;
this.$vs.loading.close("#company-profile > .con-vs-loading");
}).catch(error => {
console.log("error is in company Profile ", error);
if(error.response) {
this.isData = error.response.status.toString();
} else {
this.isData = "Network Error";
}
this.$vs.loading.close("#company-profile > .con-vs-loading");
});
},
},
};
</script>
Dashboard Card (where I'm applying the Blur effect)
<template>
<BaseTHCard
:title="title"
:titleColor="titleColor"
:actionable="actionable"
:fixed-height="fixedHeight"
#click.native="componentClicked"
>
<blur :isData="isData">
<!-- DEFAULT TRADEHAT CARD HEADER SLOT -->
<div slot="header">
<slot name="header"> </slot>
</div>
<!-- DEFAULT TRADEHAT CARD MEDIA SLOT -->
<div slot="media">
<slot name="media"> </slot>
</div>
<!-- DEFAULT TRADEHAT CARD BODY SLOT -->
<slot></slot>
<!-- DEFAULT TRADEHAT CARD EXTRA CONTENT SLOT -->
<div slot="extra-content">
<slot name="extra-content"> </slot>
</div>
<!-- DEFAULT TRADEHAT CARD FOOTER SLOT -->
<div slot="footer">
<slot name="footer"> </slot>
</div>
</blur>
</BaseTHCard>
</template>
<script>
import BaseTHCard from "#/components/common/base-th-card";
import blur from "../../ticker-dashboard/shared/Blur";
export default {
name: "DashboardCard",
extends: BaseTHCard,
components: {
BaseTHCard,
blur
},
props: {
isData: {
type: String,
},
title: {
type: String,
default: null,
},
titleColor: {
type: String,
default: "white",
},
fixedHeight: {
type: Boolean,
default: false,
},
actionable: {
type: Boolean,
default: false,
},
},
};
</script>
I'm getting two following warnings from vue:-
I am setting the isData data option in the CompanyProfile component and passing it as a prop to DashboardCard component and sending that isData prop value to the blurr component. The functionality works fine but I'm getting the above mentioned warnings from vue. What should be the approach to fix them.

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.

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)
},
...

how to send input data value from child component data object to parent?

I am trying to send input data variables from child component form to parent component through dataobject 'data()'. I have seen vuejs update parent data from child component article and tried to do it but, i am unable to $emit captured dataobject through an event. can you please help me out.
Parent component:
<script>
import inputform from '../components/form.vue';
import axios from 'axios';
export default {
name: 'helloword',
data() {
return {
}
},
components: {
inputform
},
methods: {
submit() {
const path = 'http://127.0.0.1:5000';
axios.post(path, {
name: inputform.data()
})
.then(() => {
const result = 'Sucess!';
console.log(result);
})
.catch((error) => {
console.log(error);
})
}
}
}
</script>
Child component:
<template>
<div>
<table>
<thead>
<th>Name</th>
<th>Email</th>
<th>Age</th>
</thead>
<tr>
<td><input type="text" id="name" v-model="details.name" #focusout="inputdata"></td>
<td><input type="text" id="name1" v-model="details.name1" #focusout="inputdata" ></td>
<td><input type="number" id="age" v-model="details.age" #focusout="inputdata" ></td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "inputform",
data() {
return {
details: {
name: '',
name1: '',
age: ''
}
}
},
methods: {
inputdata() {
this.$emit("input_data", this.details)
}
}
}
</script>
<style scoped>
</style>
So, looking for help with emitting variable data from child compnent to parent and perform submit operation to API using axios from parent component. If there is any other better way please let me know. Thanks.
When attaching a v-model you don't need a v-on. You could also look to capture details into a single object like so and then pass it as part of the event emitted.
Child component
<template>
<div>
<table>
<thead>
<th>Name</th>
<th>Email</th>
<th>Age</th>
</thead>
<tr>
<td>
<input type="text" id="name" v-model="details.name">
</td>
<td>
<input type="email" id="email" v-model="details.email">
</td>
<td>
<input type="number" id="age" v-model="details.age">
</td>
<td>
<button #click="inputdata">Submit</button>
</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "inputform",
data() {
return {
details: {
name: "",
email: "",
age: ""
}
};
},
methods: {
inputdata() {
console.log(this.details);
this.$emit("handledata", this.details);
}
}
};
</script>
Parent component
<template>
<div id="app">
<HelloWorld v-on:handledata="handleInput"/>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld
},
methods: {
handleInput(data) {
// object emitted from the child component
console.log({ data });
}
}
};
</script>
well first you should pass max two params to $emit method here's the docs: https://v2.vuejs.org/v2/api/#vm-emit and second is the v-on: before v-models is extra.
so the solution you can pass this data in one object instead of three data so the code will be like this:
data() {
return {
name: '',
email: '',
age: '',
}
},
methods: {
inputdata() {
this.$emit("input", {
name: this.name,
email: this.email,
age: this.age
})
}
}
or my prefer option put all in a form data like this
<template>
<div>
<table>
<thead>
<th>Name</th>
<th>Email</th>
<th>Age</th>
</thead>
<tr>
<td><input type="text" id="name" v-model="form.name"></td>
<td><input type="email" id="email" v-model="form.email"></td>
<td><input type="number" id="age" v-model="form.age"></td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "inputform",
data() {
return {
form: {
name: '',
email: '',
age: '',
}
}
},
methods: {
inputdata() {
this.$emit("input", this.form)
}
}
}
</script>