Why variable is not defined in created() method - javascript

I've got code like this. I would like to split deals array into 3 separate arrays: dealsCol1, dealsCol2, dealsCol2 and I would like to have it done after the object is created. I'm a beginner in JavaScript. I previously programmed in C++ and the thing that I guess I need is something like a constructor. I've found out that created() function works as a constructor (it's called on object creation). I put the part of the code that splits the array in that function, but I get an error:
vue.esm.js:591 [Vue warn]: Error in created hook: "ReferenceError: deals is not defined"
I have no idea why it's not defined there, because I guess that it should be. Could you give me some hints how can I solve the problem?
<script>
export default {
props: {
deals: Array
},
data() {
return {
dealsCol1: [],
dealsCol2: [],
dealsCol3: []
};
},
created() { // why it doesn't work??
this.dealsCol1 = this.deals.slice(0, this.deals.length/3),
this.dealsCol2 = this.deals.slice(this.deals.length/3, 2*this.deals.length/3),
this.dealsCol3 = this.deals.slice(2*this.deals.length/3, this.deals.length-1)
}
};
</script>
EDIT:
If I use the trick with computed() from one of the answers everything works good. But I'm wondering why the deals are visible in every other method beyond constructed(). It is also visible in the template part. Why is that?
The parent component code looks like this:
<template>
<div>
<editDealsModal ref="editDealsModal" :deals="deals" #editDeals="editDeals" />
<table class="table table-sm color mb-2">
<caption class="caption table-caption">Users</caption>
<thead>
<th class="text-left text-nowrap">Deals</th>
<th></th>
<th></th>
<th></th>
<th></th>
</thead>
<tbody>
<tr v-for="user in users" :key=user.Id v-bind:class="[{ disabled: user.IsEnabled == false }]">
<td class="text-left align-middle">{{user.Username}}</td>
<td class="text-left align-middle">
<div v-for="role in user.Roles" :key=role>{{role}}</div>
</td>
<td class="text-left align-middle">
<div v-for="deal in user.Deals" :key=deal>{{deal}}</div>
</td>
<td class="align-middle">
<b-btn variant="link" #click="showEditDealsModal(user)" v-bind:disabled="!user.IsEnabled">Edit deals</b-btn>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import service from '../../services/HttpRequestService'
import EditDealsModal from './EditDealsModal'
export default {
props: {
users: Array,
deals: Array
},
methods: {
showEditDealsModal(user) {
this.$refs.editDealsModal.showModal(user, user.Deals || [])
},
async editDeals(user, data) {
try {
await service.editDeals(user.Id, data);
this.$emit("userEdited", { type: "success", msg: "Updated deals for " + user.Username })
} catch (err) {
this.$emit("userEdited", { type: "danger", msg: "Failed to edit deals for " + user.Username })
}
},
},
components: {
EditDealsModal
}
}
</script>
enter code here

Try to defined a default value for your myData array, like this
props: {
myData: {
default: [],
type: Array,
},
},
By the way props are used to pass data from parent to child, I don't think this is the best way to do this.

myDataCol1,2,3 should be computed properties :
props: {
myData: {
default: [],
type: Array,
},
},
computed: {
myDataCol1(){
return this.myData.slice(0, this.myData.length/3)
}
.
.
.
}

Related

How to mock a Vuex store in VueJS test-utils parentComponent

I'm using Jest with vue-test-utils trying to test if a child component reacts to an $emit event in the parent component.
VueJS test-utils library provides a parentComponent option to be passed when mounting/shallow mounting the component.
Everything is working fine except that even though I instantiate the component with a mocked Vuex store, the parent component throws a
TypeError: Cannot read property 'state' of undefined
on a this.$store.state.something.here piece of code in the parent component.
How can I mock the Vuex store there?
The component mount looks like this:
const wrapper = shallowMount(ChildComponent, {
store,
localVue,
parentComponent: ParentComponent,
mocks: {
$t: msg => msg,
},
});
Any ideas on how to solve this?
it may not be the complete answer to OP questions, but since I had been debugging around for the last 2h and finally found MY problem, I would like to posted here to help someone in the future.
I was trying to mock and mount the following component:
<template>
<div test="object-list-div">
<h1 test="component-title">{{ objectName }}</h1>
<table class="table">
<thead>
<tr test="table-row-title">
<th scope="col" test="table-column-title" v-for="(value, name, index) in objectData[0]" :key="index">{{ name }}</th>
</tr>
</thead>
<tbody>
<tr test="table-row-data" v-for="(ivalue, iname, i) in objectData" :key="i">
<td test="table-cell-data" v-for="(jvalue, jname, j) in ivalue" :key="j">{{ jvalue }}</td>
</tr>
</tbody>
</table>
</div>
export default {
props: [
'objectName',
'objectData'
],
computed: {
visibleColums() {
return this.$store.state.Config_ShowColumn;
}
}
}
with the following wrapper code
wrapper = shallowMount(ObjectList, {
mocks: {
$store: {
state: {
Config_ShowColumn: [
"Field1",
"Field2",
"Field3",
"Field4",
"Field5",
]
}
}
}
});
I got OP error, but in my case the component was expecting two Props at the moment of creation. Since it did not receive this, it got stuck.
This is working now:
import { shallowMount } from "#vue/test-utils";
import { expect } from "chai";
import ObjectList from "#/components/Object-List.vue";
wrapper = shallowMount(ObjectList, {
propsData: {
objectName: "Ticket",
objectData: [
{
Field1: "Field1",
Field2: "Field2",
Field3: "Field3",
Field4: "Field4",
Field5: "Field5",
},
]
},
mocks: {
$store: {
state: {
Config_ShowColumn: [
"Field1",
"Field2",
"Field3",
"Field4",
"Field5",
]
}
}
}
});
Hope it helps someone.
Tried the solution Richard proposed but without much success, even though his guess was right.
The solution was far simnpler than I envisioned, I just stopped instantiating the Vuex.Store and just have the mocked $store in vue-test-utils config like so:
import { createLocalVue, shallowMount, config } from '#vue/test-utils';
config.mocks.$store = {
state: {
user: {
sexy: true
},
},
};
I had no need to use an actual instance of Vuex as I only needed to mock the actual data so this worked perfectly.
How are you creating the mock store? It should be something like
const storeOptions = {
state: {...},
getters: {...},
mutations: {...}
}
const mockStore = new Vuex.Store(storeOptions)
Since this.$store is undefined, I suspect you might just be passing the options object to shallowMount.

How to keep the information of a component when going from one component to another in vue js

If I have two components, the first one is called A:
<template>
<input required type='text' v-model.trim="number">
<input type="date" v-model="date" >
<button #click='allRecords(number,date)'>ok</button>
<table >
<thead>
<th>Coordonnées</th>
</thead>
<tbody>
<tr v-for='contact in contacts'>
<td #click="seeDetails(contact.id)" > {{ contact.data.to }}
</td>
</tr>
</tbody>
</table>
</template>
<script lang="js">
import axios from 'axios';
import Vue from 'vue';
export default {
name: 'A',
props: [],
data() {
return {
contacts: [],
number: '',
date: new Date().toISOString().slice(0,10),
nombre:0
}
},
methods: {
allRecords: function(number,date) {
axios.get(`/api/contacts?number=${number}&date=${date}`)
.then(response => {
this.contacts = response.data.list;
this.nombre = response.data.count;
})
.catch(error => {
console.log(error);
});
},
seeDetails (id) {
this.$router.push({ name: 'B', params: { id }});
},
}
</script>
the 2nd is called B:
<template>
<div> {{details.data.add }}</div>
</template>
<script lang="js">
import axios from 'axios';
export default {
name: 'B',
props: [],
mounted() {
const id = this.$router.currentRoute.params.id;
this.fetchContactData(id);
},
data() {
return {
details: []
}
},
methods: {
fetchContactData(id){
axios.get(`/api/recherche/${id}`)
.then(response => {
this.details = response.data
})
.catch(error => {
console.log(error);
});
},
},
}
</script>
I would like when I leave my component B has my component A to have the information of A which corespondent to the result that I had in B without needing to enter again the date and the number.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
You have waded into the problem of application state, and views can differ. The recommended solution is vuex. For simple situations, I like to keep app state in a global javascript variable. Your vue components don't need to pass state, but they refer to a single source of truth outside of vue, which they can all display and modify. So you're app state is a contacts array, and your B component, which needs a better name, will just push rows onto this array. When you return to A, your page will reflect the new data.
I see you want to show the details of a specific contact based on its ID.
But is your router configured correctly?
Dynamic Route Matching:
routes: [
{ path: '/contacts/:contact', component: B }
]
See more at https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes

VueJS Render VNode

tl;dr:
Given a VueJS VNode object, how do I get the HTML element that would be generated if it were rendered?
e.g.:
> temp1
VNode {tag: "h1", data: undefined, children: Array(1), text: undefined, elm: undefined, …}
> temp1.children[0]
VNode {tag: undefined, data: undefined, children: undefined, text: "Test", elm: undefined, …}
> doSomething(temp1)
<h1>Test</h1>
Goal
I'm attempting to build a small VueJS wrapper around the DataTables.net library.
To mimic the behavior of HTML tables in my markup, I want something like the following:
<datatable>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<datatable-row v-for="person in people">
<td>{{ person.name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.salary }}</td>
</datatable-row>
</tbody>
</datatable>
What I've done so far
I've started to implement this as follows:
DataTable.vue
<template>
<table ref="table" class="display table table-striped" cellspacing="0" width="100%">
<slot></slot>
</table>
</template>
<script>
/* global $ */
export default {
data: () => ({
instance: null
}),
mounted() {
this.instance = $(this.$refs.table).dataTable();
this.$el.addEventListener("dt.row_added", function(e) {
this.addRow(e.detail);
});
},
methods: {
addRow(row) {
// TODO <-----
console.log(row);
}
}
};
</script>
DataTableRow.vue
<script>
/* global CustomEvent */
export default {
mounted() {
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dt.row_added", {
bubbles: true,
detail: this.$slots.default.filter(col => col.tag === "td")
}));
});
},
render() { return ""; }
};
What this currently does:
When the page loads, the DataTable is initialized. So the column headers are properly formatted and I see "Showing 0 to 0 of 0 entries" in the bottom left
The CustomEvent is able to bubble up past the <tbody> and be caught by the DataTable element successfully (circumventing the limitation in VueJS that you can't listen to events on slots)
What this does not do:
Actually add the row
My event is giving me an array of VNode objects. There's one VNode per column in my row. The DataTables API has an addRow function which can be called like so:
this.instance.row.add(["col1", "col2", "col3"]);
In my case, I want the resultant element from the rendering of the VNode to be the elements in this array.
var elems = [];
for (var i = 0; i < row.length; i++)
elems[i] = compile(row[i]);
this.instance.row.add(elems);
Unfortunately this compile method eludes me. I tried skimming the VueJS documentation and I tried Googling it, but no dice. I tried manually passing the createElement function (the parameter passed to the render method) but this threw an error. How can I ask VueJS to render a VNode without injecting the result into the DOM?
I ran into the same issue wanting to do basically the same thing with a row details template for DataTables.net.
One solution could be to create a generic component that renders out a VNode and instantiate that programmatically. Here is how my setup for a dynamic detail row that I insert using datatable's row.child() API.
RenderNode.js
export default {
props: ['node'],
render(h, context) {
return this.node ? this.node : ''
}
}
Datatables.vue
Include the renderer component from above
import Vue from 'vue'
import nodeRenderer from './RenderNode'
Instantiate and mount the renderer to get the compiled HTML
// Assume we have `myVNode` and want its compiled HTML
const DetailConstructor = Vue.extend(nodeRenderer)
const detailRenderer = new DetailConstructor({
propsData: {
node: myVNode
}
})
detailRenderer.$mount()
// detailRenderer.$el is now a compiled DOM element
row.child(detailRenderer.$el).show()
You should define your components like with:
import {createApp} from 'vue';
import {defineAsyncComponent} from "vue";
createApp({
components: {
'top-bar': defineAsyncComponent(() => import('#Partials/top-bar'))
}
}).mount("#app")

How to display async data in vue template

I'm interesting in the case of displaying in vue template data which loaded asynchroniously. In my particular situation I need to show title attribute of product object:
<td class="deals__cell deals__cell_title">{{ getProduct(deal.metal).title }}</td>
But the product isn't currently loaded so that the title isn't rendered at all. I found a working solution: if the products aren't loaded then recall getProduct function after the promise will be resolved:
getProduct (id) {
if (!this.rolledMetal.all.length) {
this.getRolledMetal()
.then(() => {
this.getProduct(id)
})
return {
title: ''
}
} else {
return this.getRolledMetalById(id)
}
}
However maybe you know more elegant solution because I think this one is a little bit sophisticated :)
I always use a loader or a spinner when data is loading!
<template>
<table>
<thead>
<tr>
<th>One</th>
<th>Two</th>
<th>Three</th>
</tr>
</thead>
<tbody>
<template v-if="loading">
<spinner></spinner> <!-- here use a loaded you prefer -->
</template>
<template v-else>
<tr v-for="row in rows">
<td>{{ row.name }}</td>
<td>{{ row.lastName }}</td>
</tr>
</template>
</tbody>
</table>
</template>
And the script:
<script>
import axios from 'axios'
export default {
data() {
return {
loading: false,
rows: []
}
},
created() {
this.getDataFromApi()
},
methods: {
getDataFromApi() {
this.loading = true
axios.get('/youApiUrl')
.then(response => {
this.loading = false
this.rows = response.data
})
.catch(error => {
this.loading = false
console.log(error)
})
}
}
}
</script>
There are a few good methods of handling async data in Vue.
Call a method that fetches the data in the created lifecycle hook that assigns it to a data property. This means that your component has a method for fetching the data and a data property for storing it.
Dispatch a Vuex action that fetches the data. The component has a computed property that gets the data from Vuex. This means that the function for fetching the data is in Vuex and your component has a computed property for accessing it.
In this case, it looks like your component needs to have a RolledMetal and based on that it retrieves a product. To solve this you can add methods that fetch both of them, and call them on the created lifecycle. The second method should be called in a then-block after the first one to ensure it works as expected.

Vuejs undefined property error but it is defined

I have a simple Vue component that simply list server connection data:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<h2 class="title">Data</h2>
</div>
<br>
</div>
<div class="col-xs-12">
<table class="table">
<tr>
<td>Server</td>
<td><strong>{{config.servers}}</strong></td>
</tr>
<tr>
<td>Port</td>
<td><strong>{{config.port}}</strong></td>
</tr>
<tr>
<td>Description</td>
<td><strong>{{config.description}}</strong></td>
</tr>
<tr>
<td>Protocol</td>
<td :class="{'text-success': isHttps}">
<i v-if="isHttps" class="fa fa-lock"></i>
<strong>{{config.scheme}}</strong>
</td>
</tr>
</table>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Application',
data () {
return {
config: {
scheme: '',
servers: '',
port: '',
description: ''
}
}
},
computed: {
...mapState(['server']),
isHttps: () => this.config.scheme === 'https'
},
mounted () {
const matched = this.server.match(/(https?):\/\/(.+):(\d+)/)
this.config = {
scheme: matched[1],
servers: matched[2],
port: matched[3],
description: window.location.hostname.split('.')[0] || 'Server'
}
}
}
</script>
The server from Vuex is already defined and done when this component is mounted, and if I try to console.log(this.server), it shows the correct URL. The thing is, my computed property isHttps throws the following error:
[Vue warn]: Error in render function: "TypeError: Cannot read property 'scheme' of undefined"
found in
---> <Application> at src/pages/Aplicativo.vue
<App> at src/App.vue
<Root>
I've already tried to change config to something else, like configuration or details, and even changed mounted to created, but the error keeps popping up and my template is not rendered at all.
Firstly I began making config a computed property, but the error was already making its way to my console. By the way, using store as a computed property like this also throws an error saying my $store is undefined:
server: () => this.$store.state.server
What can I do?
You are using an arrow function for your isHttps computed. In that context, this refers to window not the Vue instance, so you will get a cannot read property of undefined message, the correct ES2015 syntax is:
isHttps() {
return this.config.scheme === 'https'
}
That is also the same problem with server: () => this.$store.state.server which should be:
server() {
return this.$store.state.server
}

Categories