Vue, Vuex & remote storage - javascript

Well a have some value in remote storage (lets say x) and b-form-checkbox that should control this value. I want to inform user if value actually changed on storage and time when it happens.
So basically I want:
When user check/uncheck b-form-checkbox I want to change state of b-form-checkbox, send async request to the remote storage and show some b-spinner to indicate that state isn't actually changed yet.
When I receive answer from remote storage:
if change was successful just hide b-spinner.
if change was not successful (timeouted, error on server, etc) I want to change b-form-checkbox state back (since value actually doesn't changed on storage) and hide b-spinner
What is the silliest way to do int using Vue + Vuex?
Currently I'm doing it this way:
xChanger.vue:
<template>
<b-form-checkbox v-model="xComp" switch>
{{xComp ? 'On' : 'Off'}}
<b-spinner v-if="!xSynced"/>
</b-form-checkbox>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
export default {
name: 'XChanger',
computed: {
...mapState(['x']),
...mapGetters(['xSynced']),
xComp: {
get() { return x.local },
set(value) {
if (value != this.x.local) {
this.setX(value)
}
},
},
},
methods: {
...mapActions(['setX']),
},
}
</script>
main.js
import Vuex from 'vuex'
import Axios from 'axios'
const store = new Vuex.Store({
state: {
x: {
remote: null,
local: null
},
getters: {
xSynced(state) {
state.x.local === state.x.remote
}
},
actions: {
async setX(store, value) {
store.state.x.local = value
try {
let response = await Axios.post('http://blah.blah/setX', {x: value});
if (response.status == 200) {
store.state.x.remote = value
}
} catch (error) {
store.state.x.local = !value
}
}
},
mutations: {
setX(state, value) {
state.x.local = value
state.x.remote = value
}
}
},
})
But it is too verbose for just one value to be controlled (especially computed property xComp). I'm sure that such a simple template should be already solved and has more simple way to implement.

Here is an example:
<template>
<b-form-checkbox v-model="x.local" switch>
{{x.local ? 'On' : 'Off'}}
<b-spinner v-if="saving"/>
</b-form-checkbox>
</template>
<script>
export default
{
name: 'XChanger',
data: () => ({
x:
{
local: false,
remote: false,
},
saving: false,
}),
watch:
{
'x.local'(newValue, oldValue)
{
if (newValue !== oldValue && newValue !== this.x.remote)
{
this.updateRemote(newValue);
}
}
}
methods:
{
async updateRemote(value)
{
try
{
this.saving = true;
const response = await Axios.post('http://blah.blah/setX', {x: value});
if (response.status == 200)
{
this.x.remote = value;
}
else
{
this.x.local = this.x.remote;
}
}
catch (error)
{
this.x.local = this.x.remote;
}
this.saving = false;
}
},
}
</script>

Related

Pinia|Vue3 I can't access the property of the object that returned from the Pinia action

first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']

Old prop keeps coming back when there's a change on Vue Currency Input

I'm using the Vue Currency Input to an input on the app that I'm working on, I'm not sure why I have this weird issue when the old prop comes back even thought no change was fired. The behaviour is: I edit the price and save, but when I click to be editable again the old price pops up.
This is my code:
<template>
<div>
<input ref="input" :value="val" v-currency="options" allow-negative="false" class="editableValue" #change="change" #keydown.enter.prevent>
</div>
</template>
<script>
import { parse } from "vue-currency-input";
import { UTILS } from "#/helpers/utils.js";
export default {
name: "EditableValue",
data(){
return {
val: this.value
}
},
props: {
currency: {
type: Object
},
value: {
type: Number
}
},
computed: {
options() {
return {
autoDecimalMode: true,
currency: {prefix: this.currency.currency + " "},
distractionFree: false
};
},
},
methods: {
change(){
console.log('chamou')
let newValue = parse(this.$refs.input.value, this.options);
this.val = newValue;
this.$emit('finishEditPrice', newValue)
},
},
watch: {
value(current) {
let inputValue = this.$refs.input.value;
let formattedValue = UTILS.getFormattedPrice(current, this.currency);
if (inputValue != formattedValue) {
this.val = formattedValue;
this.$refs.input.value = formattedValue;
}
},
},
updated(){
// if (inputValue != formattedValue) {
// this.val = formattedValue;
// this.$refs.input.value = formattedValue;
// }
}
};
</script>
The updated() and watch was a trial to change this behaviour but no luck. I'm using this version "vue-currency-input": "^1.22.6". Does anyone have any idea how to solve it? Thanks!

Vuex getters has undefined data when I try to load data from API and use it in multiple components

I have a page component where I am making api call and storing the data in Vuex store through actions. This data has to be used at multiple places but everywhere I'm initially getting undefined data which loads after a few seconds asynchronously from the API. How should I use vuex getters asynchronously ?
Here's the code for my vuex store module :
import axios from 'axios';
const state = {
all_pokemon: {},
pokemon_details: {}
};
const getters = {
getAllPokemon: function(state) {
return state.all_pokemon;
},
getPokemonDetails: function(state) {
return state.pokemon_details;
}
};
const mutations = {
setAllPokemon: function(state, payload) {
return state.all_pokemon = payload;
},
setPokemon: function(state, payload) {
console.log('Pokemon details set with payload ', payload);
return state.pokemon_details = payload;
}
};
const actions = {
setPokemonAction: function({ commit }, passed_pokemon) {
axios.get('https://pokeapi.co/api/v2/pokemon/' + passed_pokemon)
.then((response) => {
console.log('Response data is : ', response.data);
});
commit('setAllPokemon', response.data);
},
setPokemonDetailAction: function({ commit }, passed_pokemon) {
console.log('Action method called..', passed_pokemon);
axios.get('https://pokeapi.co/api/v2/pokemon/' + passed_pokemon)
.then((response) => {
commit('setPokemon', response.data);
});
}
};
export default {
state,
getters,
mutations,
actions,
};
And code for the component where I want to get this data and pass it to other components :
<script>
import { mapGetters, mapActions } from 'vuex'
import axios from 'axios'
// Imported other components here
export default {
name: 'pokemon_detail_page',
data() {
return {
current_pokemon: this.$route.params.pokemon,
isLoading: false,
childDataLoaded: false,
search_pokemon: '',
sprites: [],
childData: 'False',
isAdded: false,
pokemon_added: 'none_display',
show: false
}
},
methods: {
...mapActions([
'setPokemonDetailAction',
'removePokemon'
]),
},
computed: {
...mapGetters([
'getPokemonDetails',
'getTeam'
])
},
components: {
Game_index,
PulseLoader,
PokemonComponent,
},
filters: {
},
watch: {
getTeam: function (val) {
},
getPokemonDetails: function(val) {
}
},
created() {
setTimeout(() => {
this.show = true;
}, 2000);
this.$store.dispatch('setPokemonDetailAction', this.current_pokemon)
.then(() => {
// const { abilities, name, order, species, } = {...this.getPokemonDetails};
})
},
mounted() {
},
}
</script>
And here's is the code for the template where I'm passing this data to multiple components :
<div v-if="show" class="pokemon_stats_container" :key="childData">
<ability-component
:abilities="getPokemonDetails.abilities"
>
</ability-component>
<sprites-component
:sprites="sprites"
>
</sprites-component>
<location-component
:location_area="getPokemonDetails.location_area_encounters"
:id="getPokemonDetails.id"
>
</location-component>
<stats-component
:stats="getPokemonDetails.stats"
>
</stats-component>
<game_index
:game_indices="getPokemonDetails.game_indices"
/>
<moves-component
:moves="getPokemonDetails.moves"
:pokemon_name="getPokemonDetails.name"
>
</moves-component>
</div>
As of now, I've adopted a roundabout way of doing this through setTimeout and setting a variable after 2 seconds so that data is available for other components to use. But, there has to be a more elegant way of handling vuex asynchronous data. Someone please help me in this.
Your first commit is not in the promise
commit('setAllPokemon', response.data);
make this :
axios.get('https://pokeapi.co/api/v2/pokemon/' + passed_pokemon)
.then((response) => {
console.log('Response data is : ', response.data);
commit('setAllPokemon', response.data);
});
try to use in your vue component
$forceUpdate()
when your request is end for reload the data

Vue.js: mutation for deleting a comment

I have been working on the feature of comment deleting and came across a question regarding a mutation for an action.
Here is my client:
delete_post_comment({post_id, comment_id} = {}) {
// DELETE /api/posts/:post_id/comments/:id
return this._delete_request({
path: document.apiBasicUrl + '/posts/' + post_id + '/comments/' + comment_id,
});
}
Here is my store:
import Client from '../client/client';
import ClientAlert from '../client/client_alert';
import S_Helper from '../helpers/store_helper';
const state = {
comment: {
id: 0,
body: '',
deleted: false,
},
comments: [],
};
const actions = {
deletePostComment({ params }) {
// DELETE /api/posts/:post_id/comments/:id
document.client
.delete_post_comment({ params })
.then(ca => {
S_Helper.cmt_data(ca, 'delete_comment', this);
})
.catch(error => {
ClientAlert.std_fail_with_err(error);
});
},
};
delete_comment(context, id) {
context.comment = comment.map(comment => {
if (!!comment.id && comment.id === id) {
comment.deleted = true;
comment.body = '';
}
});
},
};
export default {
state,
actions,
mutations,
getters,
};
I am not quite sure if I wrote my mutation correctly. So far, when I am calling the action via on-click inside the component, nothing is happening.
Guessing you are using vuex the flow should be:
according to this flow, on the component template
#click="buttonAction(someParams)"
vm instance, methods object:
buttonAction(someParams) {
this.$store.dispatch('triggerActionMethod', { 'something_else': someParams })
}
vuex actions - Use actions for the logic, ajax call ecc.
triggerActionMethod: ({commit}, params) => {
commit('SOME_TRANSATION_NAME', params)
}
vuex mutations - Use mutation to make the changes into your state
'SOME_TRANSATION_NAME' (state, data) { state.SOME_ARG = data }

Vuex how to pass state/getter in data prop

I retrieved my data from database using axios under my vuex
const state = {
giveaways: null
}
const actions = {
getGiveAways : ({commit}) =>{
axios({
url : '/prod/api/thresholds_settings',
method: 'post',
data : {
},
config: 'JOSN'
})
.then(response=>{
if(response.status == 200){
//console.log(response.data.total_giveaways);
commit('SET_GIVEAWAYS', response.data.total_giveaways)
}
})
.catch(error=>{
if(error.response){
console.log('something happened')
}
});
}
}
const mutations = {
SET_GIVEAWAYS : (state, obj)=>{
state.giveaways = obj
}
}
const getters = {
doneGiveAways(state){
return state.giveaways
}
}
In my Dashboard.vue I have
import {mapState,mapGetters} from 'vuex'
export default{
data: () => ({
cards: [],
giveaways: ''
}),
computed:{
...mapState({
Giveaway: state => state.Threshold.giveaways
}),
doneGiveAways(){
return this.$store.getters.doneGiveAways
}
},
ready(){
//giveaways: this.Giveaways
//console.log(this.Giveaways);
},
created(){
const self = this
this.cards[0].result_val = 2
this.cards[2].result_val = 2;
},
mounted(){
this.$store.dispatch('getGiveAways');
console.log(this.giveaways);
}
}
My problem is I need to pass the value from the mapState Giveaway to my returning data giveaways: '' so when page fires I can get the response value using this.giveaways. If I just call {{ Giveaway }} in my html it shows the value. But I need to make something like this.giveaways = this.$store.state.Thresholds.giveaways
I would use Stephan-v's recommendation and delete the local copy of giveaways. But I don't know what your specific reason is for declaring an extra copy of giveaways, so here is a solution that will work:
In your Dashboard.vue add:
export default {
...
watch: {
Giveaway(value) {
this.giveaways = value
}
},
...
}
Just delete the giveaways property from your data Object and rename the computed doneGiveAways to giveaways and you are done.
There is no need for a local component giveaway data property in this scenario.

Categories

Resources