Vuex getters + dev tools - javascript

I started working on a large project that uses extensive number of vuex getters. The problem is, that large amount of them rely to be called in specific order or at specific time to have correct data from other getters. Looks like vue dev tools is trying to access those values, calling the getters not in expected order so some values are not correct and the app is throwing errors. Because of this I cannot use the dev tools and cannot find a simple way around it.
Here is simple snippet reproducing the issue.
This code will run just fine, but once you open dev tools, it tries to access the getter selselectedPrice which returns error until we have some data.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vuex#3.1.1/dist/vuex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in goodThings" :key="item.title">{{ item.title }}</li>
</ul>
{{totalPrice}}
</div>
<script>
const getSomeRandomData = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
title: 'fasfasf',
price: 20
},
{
title: 'asgfasgasg',
price: 30
},
{
title: 'asgasgasg',
price: 40
}
])
}, 1000)
})
}
const store = new Vuex.Store({
state: {
selectedItem: undefined,
warehouse: []
},
actions: {
fetchData: async function ({ commit }) {
const response = await getSomeRandomData()
commit("SET_WAREHOUSE", response)
}
},
mutations: {
SET_WAREHOUSE: (state, payload) => {
state.warehouse = [...payload]
}
},
getters: {
selselectedPrice: (state) => {
return state.warehouse[state.selectedItem].price
},
goodThings: (state) => {
return state.warehouse
},
totalPrice: (state) => {
return state.warehouse.reduce((a, item) => a+item.price, 0)
}
}
})
Vue.use(Vuex)
let app = new Vue({
el: '#app',
store,
created() {
this.$store.dispatch('fetchData')
},
computed: {
goodThings() {
return this.$store.getters.goodThings
},
totalPrice() {
return this.$store.getters.totalPrice
}
}
})
</script>
</body>
</html>

Related

javascript promise operation to update HTML

I think I understand promises, but I can't seem to get my head around it correctly. I have an application built in SharePoint 2013 that does a REST call via sprLib to populate an object. The object can be manipulated on the page, either entries added/deleted/updated, basically full CRUD. Then the changes are pushed back to the list. So far, this is all working smoothly. What I am trying to do now is on the update, empty the div tag, then call the initial load function again to populate the div with the new current list. Right now, either I get an error on the promise syntax or race condition where the initial load function runs again before the update finishes, so the populated list is the old list. I created a basic version to simulate the idea, minus the REST updates since I couldn't find a way to replicate that in a sandbox.
https://jsfiddle.net/36muca0g/
Long story short, 3 things should happen: 1) initial load 2) changes made 3) re-load data and empty/append html accordingly.
const fullList = [ //In real environment, this is populate via REST operation.
{ id: "12", title: "A", code: "110" },
{ id: "23", title: "B", code: "120" },
{ id: "13", title: "C", code: "130" },
{ id: "43", title: "D", code: "140" },
{ id: "52", title: "E", code: "150" },
]
$(document).ready(function () {
initialLoad(fullList);
$('#updateList').click(function() {
let item = $('#txtValue').val();
addToList(item).then(function(result){
console.log('add complete');
initialLoad(fullList);
console.log('reloaded');
})
.catch(function(){
console.log('fail');
})
});
})
function initialLoad(list) {
//In real environment, this is a getItem operation using sprLib.
$('#itemList').empty();
let listHTML = ''
for (i = 0; i < list.length; i++) {
listHTML += '<p>'+list[i].title+' ' +list[i].code+'</p>'
}
$('#itemList').append(listHTML);
}
function addToList(entry) {
return new Promise(function(resolve, reject) {
console.log('I go first');
fullList.push({ 'id': "", 'title': entry, 'code': ""}); //In real environment, this is an CRUD operation using sprLib.
setTimeout(() => resolve("done"), 5000);
});
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise</title>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script src="/scripts/promise.js"></script>
<link href="https://cdn.datatables.net/1.12.1/css/jquery.dataTables.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/select/1.4.0/css/select.dataTables.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>
<body><br><br>
<div class="container text-center row">
<div class="card col-2 bg-primary text-light" id="itemList">
</div>
<div class="card col-2 bg-secondary"><input type="text" class="form" id="txtValue">
<button type="button" class="btn btn-success" id="updateList">Submit</button></div>
</div>
</body>
</html>
The way i see it, you need a proper "trigger" to replace that ugly setTimeout() in a way that you allow to refresh the view only when the new data is accessible.
If you're working your application around a MCV pattern, what you could do is attach the state of your data onto an element inside the DOM, and listen for this element's state changes and use that to trigger the reload your fresh data.
I handled the case recently by building a drag-&-drop functionality where i wanted to display the content of the file only and only when the reader terminated the load task.
controller.js
const loadState = async function (type, input) {
return new Promise((resolve, reject) => {
try {
const reader = new FileReader();
if (type === "drop") {
const data = state.fileItem[0];
for (let i = 0; i < data.length; i++) {
if (data[i].kind === "file") {
const file = data[i].getAsFile();
reader.onload = async function (e) {
resolve((state.partNumber = createArr(e.target.result)));
};
reader.readAsText(file);
}
}
}
} catch (err) {
console.log(err);
}
});
};
eventHandlers.dropZone.addEventListener("drop", handleDropImport, false);
async function handleDropImport(e) {
try {
e.preventDefault();
if (!e.dataTransfer.items) return;
state.fileItem.push(e.dataTransfer.items);
await loadState("drop");
eventHandlers.queryPreview.classList.add("uploaded");
} catch (err) {
console.log(err);
}
}
As soon as the handleDropImport() is managed and only after the data is loaded inside the state (thanks to async/await), we add this class to the element of your choice...
eventHandlers.queryPreview.classList.add("uploaded");
You can then scan the DOM for that classList.add in your view.js using a mutation observer, and make it reachable using a publisher/subscriber pattern.
view.js
_addHandlerRender(handler) {
const options = { attributes: true, childList: true, subtree: true };
const observer = new MutationObserver((mutations) =>
mutations.forEach((mut) => {
if (mut.type === "attributes" && mut.attributeName === "class") {
handler();
}
})
);
observer.observe(this._parentElement, options);
}
model.js
const controlPreview = async function () {
try {
previewViews._clearPreview();
previewViews.render(model.state.partNumber);
} catch (err) {
console.log(err);
}
};
const init = function () {
previewViews._addHandlerRender(controlPreview);
};
init();

how should I use gsap.timeline() in vue3

I wanna make a motion graph by using gsap in vue3. I met a question. here is an example below. object-type variable like tl return by data() is transform to Proxy by default. And it dose not work when I config it later. So I try to define a variable in setup() without using reactive(). And it works when I config it later.
Run this example, I find some differences exists between tl1(define in setup without reactive()) and tl2.target(define in data()). You can run it on CodePen.
Could someone tell me the reason why it dosen't work when it becomes proxy object?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app" style="width: 5rem;height: 5rem;">
<button #click="moveOrigin">actionOrigin</button>
<button #click="moveProxy">actionProxy</button>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/gsap.min.js"></script>
<script>
const app = {
setup() {
const tl1 = gsap.timeline({ paused: true });
const flag = true;
return { tl1,flag };
},
data(){
const tl2 = gsap.timeline({ paused: true });
return {tl2};
},
methods: {
moveOrigin() {
console.log("tl1 Origin");
console.log(this.tl1);
if (this.flag) {
this.tl1.to("button", { y: "+=2rem", duration: 1 });
} else {
this.tl1.to("button", { y: "-=2rem", duration: 1 });
}
this.flag = !this.flag;
this.tl1.play();
},
moveProxy() {
console.log("tl2 Proxy");
console.log(this.tl2);
if (this.flag) {
this.tl2.to("button", { y: "+=2rem", duration: 1 });
} else {
this.tl2.to("button", { y: "-=2rem", duration: 1 });
}
this.flag = !this.flag;
this.tl2.play();
},
}
};
Vue.createApp(app).mount('#app');
</script>
</body>
</html>

How to use Neutralinojs os.runCommand in multiple platforms

I was trying to get network information on Windows using Nuetralinojs. How can I make my app cross platform? I want to run ifconfig command when users execute this on Linux.
I have posted my HTML and JS codes below.
let work = () => {
Neutralino.os.runCommand('ipconfig',
(data) => {
document.getElementById('neutralinoapp').innerHTML = data.stdout.replace(/\n/g, '</br>');
},
() => {
console.error('error');
}
);
}
Neutralino.init({
load: () => {
work();
},
pingSuccessCallback : () => {
},
pingFailCallback : () => {
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>NeutralinoJs</title>
<link rel="stylesheet" href="/assets/app.css">
</head>
<body>
<div id="neutralinoapp">
</div>
<script src="/neutralino.js"></script>
<script src="/assets/app.js"></script>
</body>
</html>
You can check os simply using NL_OS global variable of Neutralinojs.
If you are running cloud mode on a server window.navigator is not a solution.
Here is the modified JS function.
let work = () => {
let command = NL_OS == 'Windows' ? 'ipconfig' : 'ifconfig';
Neutralino.os.runCommand(command,
(data) => {
document.getElementById('neutralinoapp').innerHTML = data.stdout.replace(/\n/g, '</br>');
},
() => {
console.error('error');
}
);
}

Vue.js component model update

Im absolutely new in Vue framework and I need create reusable component with live BTC/LTC/XRP price
For live prices Im using Bitstamp websockets API. Here is example usage with jQuery - run this snippet, is really live.
var bitstamp = new Pusher('de504dc5763aeef9ff52')
var channel = bitstamp.subscribe('live_trades')
channel.bind('trade', function (lastTrade) {
$('p').text(lastTrade.price)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<h3>BTC/USD price</h3>
<p>loading...</p>
As you can see, its really simple. But, I need to use Vue.js component. So I created this, and its also fully functional:
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
template: '<div>{{price}}</div>',
data: function () {
return {
price: 'loading...'
}
},
created: function () {
this.update(this)
},
methods: {
update: function (current) {
var pair = current.$attrs.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + current.$attrs.pair
var channel = bitstamp.subscribe(pair)
channel.bind('trade', function (lastTrade) {
current.price = lastTrade.price
})
}
}
})
new Vue({
el: '.prices'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section class="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
But, there is big BUT. Am I using Vue right way? WHERE IS IDEAL PLACE to run Pusher? In "created" or "mounted" method? In "computed"? In "watch"? Or where? Am i doing it right? I really dont known, I started with Vue ... today :(
Looks pretty good for your first day using Vue! I would just make a few changes.
The component is reaching out and using a global, bitstamp. Generally with components, you want them to be independent, and not reaching out of themselves to get values. To that end, declare the socket as a property that can be passed in to the component.
Likewise, the pair is passed in as a property, but you do not declare it and instead, use current.$attrs.pair to get the pair. But that's not very declarative and makes it harder for anyone else to use the component. Moreover, by making it a property, you can reference it using this.pair.
When using something like a socket, you should always remember to clean up when you are done using it. In the code below, I added the unsubscribe method to do so. beforeDestroy is a typical lifecycle hook to handle these kinds of things.
Computed properties are useful for calculating values that are derived from your components data: the channel you are subscribing to is a computed property. You don't really need to do this, but its generally good practice.
A Vue can only bind to a single DOM element. You are using a class .prices which works in this case because there is only one element with that class, but could be misleading down the road.
Finally, created is an excellent place to initiate your subscription.
console.clear()
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
props:["pair", "socket"],
template: '<div>{{price}}</div>',
data() {
return {
price: 'loading...',
subscription: null
}
},
created() {
this.subscribe()
},
beforeDestroy(){
this.unsubscribe()
},
computed:{
channel(){
if (this.pair === 'btcusd')
return 'live_trades'
else
return 'live_trades_' + this.pair
}
},
methods: {
onTrade(lastTrade){
this.price = lastTrade.price
},
subscribe() {
this.subscription = this.socket.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe(){
this.subscription.unbind('trade', this.onTrade)
this.socket.unsubscribe(this.channel)
}
}
})
new Vue({
el: '#prices',
data:{
socket: bitstamp
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section id="prices">
<live-price pair="btcusd" :socket="bitstamp"></live-price>
<live-price pair="ltcusd" :socket="bitstamp"></live-price>
<live-price pair="xrpusd" :socket="bitstamp"></live-price>
</section>
Rewrited - is it ok now?
var config = {
key: 'de504dc5763aeef9ff52'
}
var store = new Vuex.Store({
state: {
pusher: null
},
mutations: {
initPusher (state, payload) {
state.pusher = new Pusher(payload.key)
}
}
})
var livePrice = {
template: '#live-price',
props: ['pair'],
data () {
return {
price: 'loading...',
subscription: null
}
},
computed: {
channel () {
return this.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + this.pair
}
},
methods: {
onTrade (lastTrade) {
this.price = lastTrade.price
},
subscribe () {
this.subscription = this.$store.state.pusher.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe () {
this.subscription.unbind('trade', this.onTrade)
this.$store.state.pusher.unsubscribe(this.channel)
}
},
created () {
this.subscribe()
},
beforeDestroy () {
this.unsubscribe()
}
}
new Vue({
el: '#prices',
store,
components: {
'live-price': livePrice
},
created () {
store.commit({
type: 'initPusher',
key: config.key
})
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.min.js"></script>
<section id="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
<template id="live-price">
<div>
{{price}}
</div>
</template>

Values not reactive once available in Vuex Store

So I'm retrieving my data from my api using vue-resource which is happening correctly, the state is updated and from the console I am able to see the values I'm requesting. My problem is that when the application loads the data from the store doesn't seem to be impacting the application on load, but if for example I change between pages the information is displayed correctly. This is leading me to believe somewhere along the way I have gotten the life cycle hooks incorrect, or I have handled the state incorrectly inside vuex.
Vuex store
import Vue from 'vue'
import Vuex from 'vuex'
import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.use(Vuex)
const state = {
twitter: 0,
instagram: 0,
youtube: 0,
twitch: 0
}
const actions = {
LOAD_METRICS: ({commit}) => {
Vue.http.get('http://109.74.195.166:2000/metrics').then(response => {
let out = [{
twitter: Number(response.body[0].twitter),
instagram: Number(response.body[0].instagram),
youtube: Number(response.body[0].youtube),
twitch: Number(response.body[0].twitch)
}]
commit('SET_METRICS', out)
}).catch((e) => {
console.log(e)
})
}
}
const mutations = {
SET_METRICS (state, obj) {
state.twitter = obj[0].twitter
state.instagram = obj[0].instagram
state.youtube = obj[0].youtube
state.twitch = obj[0].twitch
}
}
const getters = {}
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
Here I am trying to dispatch an event to gather the needed information using a mutation.
<template>
<div id="app">
<NavigationTop></NavigationTop>
<router-view></router-view>
<SocialBar></SocialBar>
<CopyrightBar></CopyrightBar>
</div>
</template>
<script>
export default {
name: 'app',
ready: function () {
this.$store.dispatch('LOAD_METRICS')
}
}
</script>
<style>
#import url('https://fonts.googleapis.com/css?family=Roboto:400,700,900');
#app {
font-family: 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: white;
background: url('./assets/Images/bodyBackground.jpg');
}
</style>
Then finally I am requesting the information inside of the component to be used by countup.js and also giving it to the method inside data.
<template>
<div class="hero">
<div class="container hero-content">
<div class="row hero-back align-items-end">
<div class="col-lg-6">
<div class="row">
<div class="col-lg-6" v-for="icons in socialIcons">
<Hero-Tile
:name="icons.name"
:icon="icons.iconName"
:count="icons.count"
:numeric="icons.numeric"
></Hero-Tile>
<h1>{{origin}}</h1>
</div>
</div>
</div>
</div>
</div>
<div class="diagonal-left-lines"></div>
<div class="home-hero-img"><img class="img-fluid" src="../../assets/Images/home-hero.jpg"></div>
</div>
</template>
<script>
import HeroTile from './Hero-Tile'
import CountUp from 'countup.js'
export default {
components: {HeroTile},
name: 'hero',
data () {
return {
origin: '',
socialIcons: [
{
name: 'twitter',
iconName: 'twitter',
count: this.$store.state.twitter,
numeric: 26000
},
{
name: 'instagram',
iconName: 'instagram',
count: this.$store.state.instagram,
numeric: 35000
},
{
name: 'youtube',
iconName: 'youtube-play',
count: this.$store.state.youtube,
numeric: 15000
},
{
name: 'twitch',
iconName: 'twitch',
count: this.$store.state.twitch,
numeric: 127000
}
]
}
},
methods: {
updateNumbers: function () {
let options = {
useEasing: true,
useGrouping: true,
separator: ',',
decimal: '.',
prefix: '',
suffix: 'K'
}
function kFormatter (num) {
return num > 999 ? (num / 1000).toFixed(1) : num
}
let twitter = new CountUp('twitter', 0, kFormatter(this.$store.state.twitter), 0, 3, options)
let instagram = new CountUp('instagram', 0, kFormatter(this.$store.state.instagram), 0, 3, options)
let youtube = new CountUp('youtube', 0, kFormatter(this.$store.state.youtube), 0, 3, options)
let twitch = new CountUp('twitch', 0, kFormatter(this.$store.state.twitch), 0, 3, options)
twitter.start()
instagram.start()
youtube.start()
twitch.start()
}
},
mounted: function () {
this.updateNumbers()
}
}
</script>
To be clear at the moment it seems to just load '0k' so it's as if there is some form of race condition occurring causing it not to actually load the information on load-up. Though I'm not sure what the correct approach is here.
This was eventually solved by what I'm going to describe as hacking as I don't actually know the exact correct answer at this time. Though what I have does work.
Points of Interest below:
Store
LOAD_METRICS: ({commit}, context) => {
console.log(context)
if (context === true) {
return new Promise((resolve) => {
resolve('loaded')
})
}
return new Promise((resolve) => {
Vue.http.get('real ip is normally here').then(response => {
let out = {
twitter: Number(response.body[0].twitter),
instagram: Number(response.body[0].instagram),
youtube: Number(response.body[0].youtube),
twitch: Number(response.body[0].twitch),
loaded: false
}
commit('SET_METRICS', out)
resolve(out)
}).catch((e) => {
console.log(e)
})
})
}
In the above I am now sending an instance of the current store.state.metrics.loaded when the dispatch event is sent. Which is then checked to see the truthness of the current value, Because the first load should always return false we then return a promise utilizing an API call while also mutating the store so we have the values from later. Thus onwards because we mutated the loaded event to be true the next further instances shall return a value of true and a new promise will be resolved so we can make sure the .then() handler is present.
Component
created: function () {
this.$store.dispatch('LOAD_METRICS', this.$store.state.metrics.loaded).then((res) => {
if (res !== 'loaded') {
this.updateNumbers(res)
} else {
this.socialIcons[0].count = this.kFormatter(this.$store.state.metrics.twitter) + 'K'
this.socialIcons[1].count = this.kFormatter(this.$store.state.metrics.instagram) + 'K'
this.socialIcons[2].count = this.kFormatter(this.$store.state.metrics.youtube) + 'K'
this.socialIcons[3].count = this.kFormatter(this.$store.state.metrics.twitch) + 'K'
}
})
}
Within our component created life cycle hook we then use the resulting values to identify the path to be taken when the components are created within the DOM again, this time just loading the values and allow normal data binding to update the DOM.
I believe there is a better method of approach then deliberating the logic of the state within the action setter and returning a promise that is essentially redundant other than for ensuring the .then() handle is present.

Categories

Resources