Axios gives an error: Cannot read property '$get' of undefined - vue.js

github code: https://github.com/aurora10/amazone-clone.git
I try to utilize Axios to hit an API but get an error:
The error in console is NUXT SSR:
TypeError: Cannot read property '$get' of undefined
at asyncData (pages/index.js:98:35)
at promisify (server.js:1898:15)
at Promise.all.Components.map.Component (server.js:1573:82)
at Array.map (<anonymous>)
at module.exports../.nuxt/server.js.__webpack_exports__.default (server.js:1569:51)
This is how I try to do it:
export default {
async asyncData({ $axios }) {
try {
let response = await $axios.$get("http://localhost:3000/api/products");
console.log(response);
return {
products: response.products
};
}catch (err) {
console.log(err);
}
}
}
The API itself works. If I call it from browser - it gives me the list of products.
What am I doing wrong?
Complete fille:
<template>
<div class="container-fluid browsing-history">
<div class="row">
<div class="col-sm-8 col-8">
<h1 class="a-size-large a-spacing-none a-text-normal">All products</h1>
<div class="a-spacing-large"></div>
Add new product
Add new category
Add new owner
</div>
</div>
</div>
<div class="a-spacing-large"></div>
<div class="container-fluid browsing-history">
<div class="row">
<div class="col-xl-2 col-lg-2 col-md-3 col-sm-6 col-6 br bb">
<div class="history-box">
<!-- product page-->
<a href="#" class="a-link-normal">
<img src="" class="img-fluid" alt="">
</a>
<div class="a-spacing-top-base asin-title">
<span class="a-text-normal">
<div class="p13n-sc-truncated">Title </div>
</span>
</div>
<div class="a-row">
<a href="#">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
</a>
<span class="a-letter-space"></span>
<span class="a-color-tertiary a-size-small asin-reviews">(1732)</span>
</div>
</div>
<!--price-->
<div class="a-row">
<span class="p13-sc-price">$25</span>
</div>
<!--byttons-->
<div class="a-row">
Update
Delete
</div>
</div>
</div>
</div>
export default {
async asyncData({$axios}) {
try {
let response = await $axios.$get("http://localhost:3000/api/products");
console.log(response);
return {
products: products
};
} catch (err) {
console.log(err);
}
}
};
I did make sure it is installed. I had also included
module.exports = {
modules: [
'#nuxtjs/axios',
],
axios: {
// proxyHeaders: false
}
}
just like they tell in the the installation manual. Also tried to remove $ before get...still the same error in console:( I have no clue what else could be wrong..

It looks like you are not including the axios module, look at the installation process here https://axios.nuxtjs.org/setup.html#install
Your config should then look like this in your export default
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
'#nuxtjs/axios'
],
axios: {
// proxyHeaders: false
}

Here are the steps for axios in Nuxt
Make sure axios is installed npm install #nuxtjs/axios
Add axios in nuxt.config.js
...
axios: {
// proxyHeaders: false
}
...
You don't need the $ in $axios.$get. It should be $axios.get()

Related

How can I add a task to a list in my Vue 2 to-do app?

I am trying to add a task to a tasklist in Vue based on the input and add task button, but I keep getting the error "taskList is not defined". Does anybody see how to fix this problem? The code is as following:
<template>
<div id="input">
<form>
<input v-model="task.name">
<button v-on:click="addTask" v-bind:value="task.name">+</button>
</form>
<ol>
<div v-for="task in taskList" :key="task.id">
{{ task.name }}
<div v-if="task.completed">
<h2> Done </h2>
</div>
<div v-else>
<h2> Not done</h2>
</div>
</div>
</ol>
</div>
</template>
<script>
export default {
name: 'AddTask',
data: function() {
return {
taskList: [
{
name: 'task', completed: false, id: 3
}
] }
},
methods: {
addTask: function (task) {
taskList.push(task);
alert('test');
}
}
}
</script>
Ps. any other Vue tips are welcome as well.
You need to separate out your taskList and the current task you're adding, decouple it as a new object, then add it to your taskList array.
When referring to items in your data object you need to use the this keyword – e.g this.taskList rather than taskList:
new Vue({
el: "#app",
data: {
id:1,
taskList: [],
currentTask:{
completed:false,
name:'',
id:this.id
}
},
methods: {
addTask: function() {
let newTask = {
completed:this.currentTask.completed,
name:this.currentTask.name,
id:this.currentTask.id
}
this.taskList.push(newTask);
this.id++;
//alert('test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="input">
<ol>
<li v-for="task in taskList" :key="task.id">
{{ task.name }}
<input type="checkbox"
:checked="task.completed"
#change="task.completed = !task.completed">
<span v-if="task.completed">
Done
</span>
<span v-else>
Not Done
</span>
</li>
</ol>
<input type="text" v-model="currentTask.name">
<button v-on:click="addTask">+</button>
</div>
</div>
From what I see in your template you use tasklist but you define it as taskList You will want to make sure your names are in the same case. Usually you'll see camelCase in vue, but other popular ones are snake_case and PascalCase

Vee-Validate Showing a BlankPage when doing the configurations

I using Vue3.0 and also use Vee-validate to validate my form. But I don't know why it keep showing blank page just like in this picture
I already do what they said in the documentations, here's my code
<form class="wrap" id="signup-form col-lg-5" #submit.prevent="processForm">
<div class="row mb-5">
<router-link :to="{'name': 'Home'}">
<span class="iconify" data-icon="ion:return-up-back-outline" data-width="25" data-height="25"></span>
<button class="btn">Back</button>
</router-link>
</div>
<!-- full name -->
<div class="form-group row">
<label for="name">Full Name <span class="text-danger">*</span></label>
<ValidationProvider rules="positive|odd" v-slot="err">
<input type="text" class="form-control" v-model.trim="name">
<span>{{ err.errors[0] }}</span>
</ValidationProvider>
</div>
<!-- submit button -->
<div class="row d-flex align-items-center">
<button type="submit" class="btn btn-outline-dark col-sm-4">Submit</button>
<p style="cursor:pointer;" class="col-sm-7">
<router-link :to="{name:'Login'}">Has an account? Login</router-link>
</p>
</div>
</form>
And here's my script
<script>
import { ValidationProvider } from 'vee-validate';
import { extend } from 'vee-validate';
extend('odd', value => {
return value % 2 !== 0;
});
extend('positive', value => {
return value >= 0;
});
export default {
name: 'RegisterForm',
components: {
ValidationProvider
},
data: function(){
return{
name: '',
}
},
methods: {
processForm() {
this.$emit('form-submit',
{
'name': this.email,
'password': this.password,
})
}
}
}
</script>
What should I change from the code? By the way, Is Vee-Validator should be assigned in main.js?
Looks like you are using vee-validate v3.x, it isn't compatible with Vue 3
vee-validate v4 was released recently that supports Vue 3, but with a completely different API
https://vee-validate.logaretm.com/v4/

Bind class item in the loop

i want to bind my button only on the element that i added to the cart, it's working well when i'm not in a loop but in a loop anything happen. i'm not sure if it was the right way to add the index like that in order to bind only the item clicked, if i don't put the index every button on the loop are binded and that's not what i want in my case.
:loading="isLoading[index]"
here the vue :
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading[index]"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
here the data :
data () {
return {
products : [],
isLoading: false,
}
},
here my add to cart method where i change the state of isLoading :
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
You can change your isLoading to an array of booleans, and your addToCart method to also have an index argument.
Data:
return {
// ...
isLoading: []
}
Methods:
addToCart(product, index) {
// ...
}
And on your button, also include index:
#click="addToCart(product, index)"
By changing isLoading to an empty array, I don't think isLoading[index] = true will be reactive since index on isLoading doesn't exist yet. So you would use Vue.set in your addToCart(product, index) such as:
this.$set(this.isLoading, index, true)
This will ensure that changes being made to isLoading will be reactive.
Hope this works for you.
add on data productsLoading: []
on add to cart click, add loop index to productsLoading.
this.productsLoading.push(index)
after http request done, remove index from productsLoading.
this.productsLoading.splice(this.productoading.indexOf(index), 1)
and check button with :loading=productsLoading.includes(index)
You can create another component only for product card,
for better option as show below
Kindly follow this steps.
place the content of card in another vue component as shown below.
<!-- Product.vue -->
<template>
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</templete>
<script>
export default {
name: "Product",
data() {
return {
isLoading: false
}
},
props: {
product: {
type: Object,
required: true
}
},
methods: {
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
}
}
</script>
Change your component content as shown below.
<template>
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<product :product="product" />
</div>
</div>
</div>
</div>
</templete>
<script>
import Product from 'path to above component'
export default {
components: {
Product
}
}
</script>
so in the above method you can reuse the component in other components as well.
Happy coding :-)

Data sent from one Vue component to another remains reactive

I have an input-component which has a form which collects start and finish times, job number and a select option.
This is attached to a data property with v-model.
This is then emitted with Event.$emit('addedData', this.hours)
In the display-component the Event.$on takes this data and checks an attribute and based on the check adds it to another data property (array) with this.todays_hours.push().
The template then displays this reactively using v-for in the template.
To this point all works fine. However when I then attempt to add another line of hours the hours already displayed change reactively with the input.
As my input-component also posts to a database with axios if I reload the page all is displayed correctly.
input-component
<template>
<div>
<div class="row">
<div class="col-2">
<input hidden="" v-model="hours.day">
</div>
<div class="col-2" >
<input type="time" v-model="hours.start">
</div>
<div class="col-2" >
<input type="time" v-model="hours.finish">
</div>
<div class="col-2" >
<input type="number" v-model="hours.job_number">
</div>
<div class="col-2" >
<select v-model="hours.climbing">
<option selected="selected" value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
<div class="col-2" >
<button #click="onSave" class="btn-success btn-sm">Save</button>
</div>
</div>
<hr>
</div>
</template>
<script>
export default {
name: 'InputHoursComponent',
props: ['employeeId', 'dayCheck', 'weekEnding'],
data() {
return {
hours: {
start: "",
finish: "",
job_number: "",
climbing: 0,
day: this.dayCheck
},
climbing_select: ['No', 'Yes'],
}
},
methods: {
onSave()
{
axios.post('/payroll', {
employee_id: this.employeeId,
week_ending: this.weekEnding,
start: this.hours.start,
finish: this.hours.finish,
job_number: this.hours.job_number,
climbing: this.hours.climbing,
day: this.dayCheck
})
.then(response => {})
.catch(e => {this.errors.push(e)});
let data = this.normalizeProp(this.hours, s, true)
Event.$emit('onAddedEntry', data);
console.log("passed data:", this.hours);
}
}
}
</script>
display-component
<template>
<div>
<div v-for="item in todays_hours">
<div class="row">
<div class="col-2">
<div hidden="" ></div>
</div>
<div class="col-2" >
<div v-text="item.start"></div>
</div>
<div class="col-2" >
<div v-text="item.finish"></div>
</div>
<div class="col-2" >
<div v-text="item.job_number"></div>
</div>
<div class="col-2" >
<div v-text="(item.climbing)?'Yes':'No'"></div>
</div>
<div class="col-2" >
<button #click="onEdit" class="btn-warning btn-sm mb-1">Edit</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DisplayHoursComponent',
props: ['dayCheck', 'hoursWorked'],
data() {
return {
hours_list: this.hoursWorked,
todays_hours: []
}
},
mounted() {
for (var i = 0; i < this.hours_list.length; i++) {
if (this.hours_list[i].day === this.dayCheck) {
this.todays_hours.push(this.hours_list[i])
}
}
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push(check);
}
})
},
methods: {
onEdit()
{
}
}
}
</script>
Can someone please help me?
Try pushing a copy of check instead of check itself.
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push({...check});
}
})
You could also make the copy when you emit the event instead.

Vue UI not updating using axios after closing semantic modal

I have a vue that displays an html table based on an axios call to get a list of points from a db table during mounted. I also have a semantic modal that I use in the same vue to add records to the points db table. During the modal's onHidden, I make the same axios call to update the html table in order to display the new record. However, the html table isn't updating.
<template>
<div>
<h1 class="ui header">Points</h1>
<button class="ui icon button" v-on:click="showModal()">
<i class="add icon"></i>
</button>
<div class="ui modal">
<div class="header">
Header Text
</div>
<div class="content">
<div class="ui form">
<div class="field">
<label>Date</label>
<datepicker v-model="newPoint.earnedDate" id="earned_date"></datepicker>
</div>
<div class="ui grid">
<div class="four wide column">
<div class="ui dropdown" id="name_dd">
<div class="text">Name</div>
<i class="dropdown icon"></i>
</div>
</div>
<div class="eight wide column">
<div class="ui dropdown" id="rule_dd">
<div class="text">Rule</div>
<i class="dropdown icon"></i>
</div>
</div>
</div>
</div>
</div>
<div class="actions">
<div class="ui black deny button">
Cancel
</div>
<div class="ui positive right labeled icon button">
Save
<i class="checkmark icon"></i>
</div>
</div>
</div>
<table class="ui celled table">
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Points</th>
</tr>
</thead>
<tbody>
<tr v-for="point in points">
<td>{{point.earnedDate}}</td>
<td>{{point.name}}</td>
<td>{{point.points}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import axios from "axios";
import Datepicker from 'vuejs-datepicker';
export default {
name: 'earnings',
components: {
Datepicker,
},
data() {
return {
points: [],
newPoint: {
earnedDate: "1/1/1970",
name: "",
points: ""
},
earners: [],
errors: [],
}
},
methods: {
showModal: function() {
$('.ui.modal')
.modal('show');
},
},
mounted () {
//THIS CALL UPDATES THE HTML TABLE
axios
.get('api/earnings')
.then(response => (this.points = response.data));
//Set the modal approve and deny callbacks
$('.ui.modal')
.modal({
closable: true,
onDeny: function () {
return;
},
onApprove: function () {
/*****************************************
* Add the new points using web API
*****************************************/
//Get the modal values
var earned_date = $('#earned_date').val();
var earner_id = $('#name_dd').dropdown('get value');
var rule_id = $('#rule_dd').dropdown('get value');
//Call the post route
axios
.post('api/earnings', { earnedDate: earned_date, earnerId: earner_id, ruleId: rule_id})
.then(response => {})
.catch(e => {
console.log(e)
})
return;
},
onHidden: function () {
//THIS CALL DOES NOT UPDATE THE HTML TABLE
axios
.get('api/earnings')
.then(response => (this.points = response.data));
}
});
//Load the name dropdown on the add new points modal
$('.four.wide.column .ui.dropdown')
.dropdown({
apiSettings: {
// this url just returns a list of tags (with API response expected above)
url: 'api/earners/semantic_dd',
cache: false
},
});
//Load the rule dropdown on the add new points modal
$('.eight.wide.column .ui.dropdown')
.dropdown({
apiSettings: {
// this url just returns a list of tags (with API response expected above)
url: 'api/rules/semantic_dd',
cache: false
},
});
},
created: function () {
// Remove the modal so it doesn't duplicate when the user navigates away from the component
// and then returns to it
$('.ui.modal').remove();
}
}
</script>
It looks like the this is not available during the axios call using =>. At the start of mounted I set var self = this and used self.points instead of this.points during onHidden. The binding to the template is working now.
Edit after-the-fact
In Vue.js, use this.$data.property-name which points to the component's properties. See documentation at https://v2.vuejs.org/v2/api/#data