How can I call a method every x seconds? - vue.js

I need to call the method getBitcoins() every second.
Tried: I tried just taking it out the methods and putting it under the
line import Pickaxe from '../assets/pickaxe.png' and then using
setInterval to call it every second, but then I can't access the data
variable btcPrice inside getBitcoins().
So I need a way to call the getBitcoins() from the methods functions every second, just as it is in the code below.
<template>
<div id="wrapper">
<div class="top"></div>
<!-- Center -->
<div class="center">
<img :src="Pickaxe" class="Pickaxe">
<span class="my-btc">{{ mybtc.toFixed(8) }}</span>
<span id="btc">1 BTC = {{ btcPrice }}</span>
<button class="Mine">Mine</button>
<span class="hashes">{{btcMin}} btc/min</span>
<button class="Upgrade">UPGRADE</button>
<span class="upgradePrice">{{ upgradePrice }}btc</span>
</div>
</div>
</template>
<script>
import bitcoin from '../assets/bitcoin.svg'
import Pickaxe from '../assets/pickaxe.png'
export default {
name: 'landing-page',
data() {
return {
bitcoin,
Pickaxe,
mybtc: 1,
btcPrice: null,
btcMin: 0,
upgradePrice: 0
}
},
methods: {
getBitcoins() {
var currentPrice = new XMLHttpRequest();
currentPrice.open('GET', 'https://api.gdax.com/products/BTC-USD/book', true);
currentPrice.onreadystatechange = function(){
if(currentPrice.readyState == 4){
let ticker = JSON.parse(currentPrice.responseText);
let price = ticker.bids[0][0];
document.getElementById('btc').innerHTML = "1 BTC = " + price + "$";
};
};
currentPrice.send();
}
}
}
</script>

I think this should work for your needs.
created() {
this.interval = setInterval(() => this.getBitcoins(), 1000);
},
It's not necessary to register this on the created event, you can register it on other method, or even on a watcher.
If you do it that way, you'll have to check somehow that it hasn't been registered, cause it may cause multiple loops to run simultaneously.

Related

Vue 3, Pinia - child component is not updated when the store is updated

I have this Pinia store:
import { defineStore } from 'pinia'
import axiosClient from '#/axios'
export const useTableOrderStore = defineStore( 'tableOrders', {
id : 'tableOrders',
state: () => {
return {
tableOrders: []
}
},
actions: {
addToOrder(item, quantity)
{
const searchIndex = this.tableOrders.findIndex((order) => order.id == item.id);
if(searchIndex !== -1)
{
this.tableOrders[searchIndex].quantity += quantity
}
else
{
item.quantity = quantity
this.tableOrders.push(item)
}
}
}
})
Parent component:
<script setup>
import {ref} from "vue"
import {useTableOrderStore} from "#/store/tableOrder";
import CounterInput from "#/components/Inputs/Counter.vue"
const tableOrderStore = useTableOrderStore()
const props = defineProps(['product'])
let quantity = ref(1)
let addToOrder = (product) => {
tableOrderStore.addToOrder(product, quantity.value)
quantity.value = 1
}
</script>
<template>
<div class="input-group">
<counter-input :quantity="quantity"
#quantity-event="(n) => quantity = n"></counter-input>
<button class="btn btn-primary btn-sm ms-1" #click="addToOrder(product)">
Add <font-awesome-icon icon="fas fa-receipt" class="ms-2" />
</button>
</div>
</template>
Child component:
<script setup>
import {ref} from "vue"
let props = defineProps({
quantity: {
type: Number,
required: true
}
})
let count = ref(props.quantity)
let increase = () => {
count.value++
emit('quantityEvent', count.value)
}
let decrease = () =>
{
if(count.value === 1)
return
count.value--
emit('quantityEvent', count.value)
}
const emit = defineEmits(['quantityEvent'])
</script>
<template>
<span class="input-group-btn">
<button type="button"
class="btn btn-danger btn-number"
data-type="minus"
#click="decrease"
>
<font-awesome-icon icon="fas fa-minus-circle" />
</button>
</span>
<input type="text"
name="quantity"
:value="count"
class="form-control input-number"
min="1"
>
<span class="input-group-btn">
<button type="button"
class="btn btn-success btn-number"
data-type="plus"
#click="increase"
>
<font-awesome-icon icon="fas fa-plus-circle" />
</button>
</span>
</template>
The first time method addToOrder is fired, the product is correctly added and child product renders is.
The first issue here is that the quantity is set to 1, but it is not set in the child component.
The second problem is with the quantity - first addToOrder is ok, and quantity is shown correctly, but if new quantity is added the Pinia store is updated, but it is not reflected in the component. What am I doing wrong here?
I guess you run into an vuejs caveat.
this.tableOrders[searchIndex].quantity += quantity
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue When you modify the length of the
array, e.g. vm.items.length = newLength
You directly set an item.
Instead, you could use .splice() to replace your item:
let newItem = {
...this.tableOrders[searchIndex],
quantity: this.tableOrders[searchIndex].quantity + quantity
};
//replace old item with new
this.tableOrders.splice(searchIndex, 1, newItem)
Here are the mutation methods that triggers an update:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
The first time you use addToOrders works because you used .push() witch is one of the mutations that triggers the re-render.
First issue, i dont know how props.quantity is not set in the child component. Try console props.quantity right after you defined props if it displayed the result as you want then try to console it inside watch method. If props.quantity has changed but your child component is not update then your child component somehow not updated. You can try to force update it and here's how: https://medium.com/emblatech/ways-to-force-vue-to-re-render-a-component-df866fbacf47 (Using the key changing technique)
Second issue, i think this one #quantity-event="(n) => quantity = n" should be #quantity-event="(n) => { quantity = n }"

Unable to Define Variable in Vue

I'm just starting to use VueJS & Tailwind, having never really used anything related to npm before.
I have the below code, making use of Tailwind & Headless UI which through debugging, I know I'm like 99% of the way there... except for the continuous error message
Uncaught ReferenceError: posts is not defined
I know this should be straight forward, but everything I've found either here or with Google hasn't worked. Where am I going wrong?
<template>
<Listbox as="div" v-model="selected">
<ListboxLabel class="">
Country
</ListboxLabel>
<div class="mt-1 relative">
<ListboxButton class="">
<span class="">
<img :src="selected.flag" alt="" class="" />
<span class="">{{ selected.name }}</span>
</span>
<span class="">
<SelectorIcon class="" aria-hidden="true" />
</span>
</ListboxButton>
<transition leave-active-class="" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions class="">
<ListboxOption as="template" v-for="country in posts" :key="country" :value="country" v-slot="{ active, selected }">
<li :class="">
<div class="">
<img :src="country.flag" alt="" class="" />
<span :class="[selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']">
{{ country.latin }}
</span>
</div>
<span v-if="selected" :class="">
<CheckIcon class="" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>
<script>
import { ref } from 'vue'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '#headlessui/vue'
import { CheckIcon, SelectorIcon } from '#heroicons/vue/solid'
import axios from 'axios'
export default {
data() {
return {
response: null,
posts: undefined,
};
},
components: {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
mounted: function() {
axios.get('http://localhost')
.then(response => {
this.posts = response.data;
});
},
setup() {
const selected = ref(posts[30])
return {
selected,
}
},
}
</script>
The offending line is const selected = ref(posts[30]) which I know I need to somehow define posts, but I don't get how?
CAUSE OF YOUR ERROR:
You are trying to access an array element before the array is populated. Thus the undefined error.
EXPLANATION
You are using a mix of composition api and options api. Stick to one.
I am writing this answer assuming you will pick the composition api.
Follow the comments in the below snippet;
<script>
// IMPORT ONMOUNTED HOOK
import { ref, onMounted } from 'vue'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '#headlessui/vue'
import { CheckIcon, SelectorIcon } from '#heroicons/vue/solid'
import axios from 'axios'
export default {
// YOU DO NOT NEED TO DEFINE THE DATA PROPERTY WHEN USING COMPOSITION API
/*data() {
return {
response: null,
posts: undefined,
};
},*/
components: {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
// YOU DO NOT NEED THESE LIFE CYCLE HOOKS; COMPOSITION API PROVIDES ITS OWN LIFECYCLE HOOKS
/*mounted: function() {
axios.get('http://localhost')
.then(response => {
this.posts = response.data;
});
},*/
setup() {
// YOU ARE TRYING TO ACCESS AN ELEMENT BEFORE THE ARRAY IS POPULATED; THUS THE ERROR
//const selected = ref(posts[30])
const posts = ref(undefined);
const selected = ref(undefined);
onMounted(()=>{
// CALL THE AXIOS METHOD FROM WITHIN THE LIFECYCLE HOOK AND HANDLE THE PROMISE LIKE A BOSS
axios.get('http://localhost')
.then((res) => {
selected.value = res[30];
});
});
return {
selected,
}
},
}
</script>
According to your comment; you should first check if the “selected != null” before using ‘selected’ inside the template. You can use a shorthand version like this
<img :src=“selected?.flag” />

VueJS - template variables not reactive with data variable

I'm making a chat system with socket.io and VueJS, so customers can talk to an admin. But when a client connects to the server, the variable in the data() updates. But the template is not updating.
Here is my code:
<template>
<div>
<div class="chats" id="chat">
<div class="chat" v-for="chat in chats">
<b>{{ chat.clientName }}</b>
<p>ID: {{ chat.clientID }}</p>
<div class="jens-button">
<img src="/icons/chat-bubble.svg">
</div>
</div>
</div>
</div>
</template>
<script>
let io = require('socket.io-client/dist/socket.io.js');
let socket = io('http://127.0.0.1:3000');
export default {
name: 'Chats',
data() {
return {
chats: [],
}
},
mounted() {
this.getClients();
this.updateClients();
},
methods: {
getClients() {
socket.emit('get clients', true);
},
updateClients() {
socket.on('update clients', (clients) => {
this.chats = clients;
console.log(this.chats);
});
}
},
}
</script>
Then I get this, the box is empty:
But I need to get this, this will only appear when I force reload the page. I don't know what I'm doing wrong...
Oke, I've found out where the problem was, in another component I used plain javascript which brokes the whole reactive stuff.

Dynamic v-model problem on nuxt application

I am trying to create a dynamic v-model input. All seems well except for the following.
The on click event that triggers the checkAnswers method only works if you click out of the input then click back into the input and then press the button again. It should trigger when the button is pressed the first time.
Does anyone have any ideas? Thanks in advance.
<template>
<div class="addition container">
<article class="tile is-child box">
<div class="questions">
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n] }} + {{ randomNumberB[n] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n]">
<p>{{ outcome[n] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answer</button>
</div>
</article>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
randomNumberB: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
userAnswer: [],
outcome: [],
}
},
methods: {
checkAnswers() {
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === (this.randomNumberA[i] + this.randomNumberB[i])) {
this.outcome[i] = 'Correct';
} else {
this.outcome[i] = 'Incorrect';
}
}
}
}
}
</script>
You have some basic issues with your use of the template syntax here. According to the vue docs:
One restriction is that each binding can only contain one single
expression, so the following will NOT work: {{ var a = 1 }}
If you want to populate your arrays with random numbers you would be better calling a function on page mount. Something like this:
mounted() {
this.fillArrays()
},
methods: {
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
Then you can use template syntax to display your arrays.
It looks like you are setting up a challenge for the user to compare answers so I think you'd be better to have a function called on input: Something like:
<input type="whatever" v-model="givenAnswer[n-1]"> <button #click="processAnswer(givenAnswer[n-1])>Submit</button>
Then have a function to compare answers.
Edit
I have basically rewritten your whole page. Basically you should be using array.push() to insert elements into an array. If you look at this you'll see the randomNumber and answer arrays are populated on page mount, the userAnswer array as it is entered and then the outcome on button click.
<template>
<div>
<div >
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n-1] }} + {{ randomNumberB[n-1] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n-1]">
<p>{{ outcome[n-1] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answers</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [],
randomNumberB: [],
answer: [],
userAnswer: [],
outcome: [],
}
},
mounted() {
this.fillArrays()
},
methods: {
checkAnswers() {
this.outcome.length = 0
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === this.answer[i]) {
this.outcome.push('Correct');
} else {
this.outcome.push('Incorrect');
}
}
},
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
}
</script>

Vue.js - v-for indicies mixed up?

What do I have: components structure
<Games> // using v-for - iterate all games
--<Game> // using v-for - iterate all players
----<Player 1>
------<DeleteWithConfirmation>
----<Player 2>
------<DeleteWithConfirmation>
----<Player 3>
------<DeleteWithConfirmation>
----...
<DeleteWithConfirmation> implementation: two clicks are required for deleting game property.
<template>
<div>
<button #click="incrementDelete"
v-html="deleteButtonHTML"></button>
<button v-if="deleteCounter === 1" #click="stopDeleting">
<i class="undo icon"></i>
</button>
</div>
</template>
<script>
export default {
name: 'DeleteWithConfirmation',
data() {
return {
deleteCounter: 0
}
},
computed: {
deleteButtonHTML: function () {
if (this.deleteCounter === 0)
return '<i class="trash icon"></i>'
else
return 'Are you sure?'
}
},
methods: {
incrementDelete() {
this.deleteCounter++
if (this.deleteCounter === 2) {
//tell parent component that deleting is confirmed.
//Parent call AJAX than.
this.$emit('deletingConfirmed')
this.stopDeleting()
}
else if (this.deleteCounter > 2)
this.stopDeleting()
},
stopDeleting() {
Object.assign(this.$data, this.$options.data())
}
}
}
</script>
My problem: seems like indicies are mixed up:
Before deleting 4th player was on "Are you sure state" (deleteCounter === 1), but after deleting it went to initial state (deleteCounter === 0). Seems like 3rd component state haven't updated its deleteCounter, but its data (player's name was updated anyway).
After successfull deleting <Games> component data is fetched again.
You don't need a delete counter for achieving this. On the contrary, it makes it hard to understand your code. Just use a boolean like this:
<template>
<div>
<button #click="clickButton"
<template v-if="confirmation">
<i class="trash icon"></i>
</template>
<template v-else>
Are you sure?
</template>
</button>
<button v-if="confirmation" #click="confirmation = false">
<i class="undo icon"></i>
</button>
</div>
</template>
<script>
export default {
name: 'DeleteWithConfirmation',
data() {
return {
confirmation: false
}
},
methods: {
clickButton() {
if (!this.confirmation) {
this.confirmation = true;
} else {
this.$emit('deleting-confirmed');
}
}
}
</script>
The parent could then be looking e.g. like this:
<div class="row" v-if="showButton">
[...]
<delete-with-confirmation #deleting-confirmed="showButton = false">
</div>
One of the answers was deleted, I wish I could mention the initial author, but I don't remeber his username, so (changed a bit):
incrementDelete() {
if (this.deleteCounter === 1) { // 1 because there is "post-increment" at the end of the fucntion
this.deletingProgress = true
this.$emit('deletingConfirmed')
this.stopDeleting()
}
else if (this.deleteCounter > 1) // 1 because there is "post-increment" at the end of the fucntion
this.stopDeleting()
this.deleteCounter++ // "post-increment"
},
ssc-hrep3 answer is more clean (and lightweight) than mine approach, link.