How to set state from an external API in Vuex store - vue.js

In my store.js I init an object
let exchangeRates = {
GBPtoUSD: 0, // American dollar
GBPtoEUR: 0, // Euro
GBPtoCAD: 0, // Canadian dollar
GBPtoINR: 0, // Indian rupee
GBPtoCHF: 0 //Swiss franc
}
Then I am setting its value with
function getExchangeRates() {
return axios.get('https://api.exchangeratesapi.io/latest?base=GBP&symbols=USD,EUR,CAD,CHF,INR')
.then((response)=> {
exchangeRates.GBPtoUSD = response.data.rates.USD;
exchangeRates.GBPtoEUR = response.data.rates.EUR;
exchangeRates.GBPtoCAD = response.data.rates.CAD;
exchangeRates.GBPtoINR = response.data.rates.INR;
exchangeRates.GBPtoCHF = response.data.rates.CHF;
})
.catch((error)=> {console.log(error)})
}
My state gets values assigned like this
state: {
GBPtoUSD: exchangeRates.GBPtoUSD, // American dollar
GBPtoEUR: exchangeRates.GBPtoEUR, // Euro
GBPtoCAD: exchangeRates.GBPtoCAD, // Canadian dollar
GBPtoINR: exchangeRates.GBPtoINR, // Indian rupee
GBPtoCHF: exchangeRates.GBPtoCHF //Swiss franc
}
Then I set up getters, such as
getGBPtoUSD: state => {
return state.GBPtoUSD;
},
The problem is, when later on I am using calling that getter in my component like this
...mapGetters( [ "getGBPtoUSD" ] )
<li class="list-group-item py-3 ">
{{ precise(userInputPrice) }}£ in USD is
{{ precise(userInputPrice * getGBPtoUSD()) }} US dollars
</li>
I am getting getGBPtoUSD as 0 - the initial state.
How do I fix this so that state's value actually get assigned from outside? My approach to doing this clearly does not work.
EDIT1
Ok so doing
exchangeRates.GBPtoUSD = response.data.rates.USD;
console.log(exchangeRates.GBPtoUSD)
logs the correct value of around 1.27. So where am I screwing up?

I've made this example on how to set the state from external service using axios
// main.js
import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";
import axios from "axios";
Vue.config.productionTip = false;
Vue.use(Vuex);
const store = new Vuex.Store({
// create the state to store the data
state: {
todo: {}
},
// make an asynchronous call to get the data
actions: {
getTodoItem(state, index) {
return axios.get(`https://jsonplaceholder.typicode.com/todos/${index}`);
}
},
// set data in state
mutations: {
setTodo(state, value) {
Vue.set(state, "todo", value);
}
}
});
new Vue({
store, // loads the store
render: h => h(App)
}).$mount("#app");
<!-- App.vue -->
<script>
import { mapState, mapActions, mapMutations } from "vuex";
export default {
computed: {
...mapState(["todo"]),
hasTodo() {
return Object.keys(this.todo).length;
}
},
methods: {
...mapActions(["getTodoItem"]),
...mapMutations(["setTodo"]),
async getTodo() {
// random number between 1 and 200
const index = Math.floor(Math.random() * 200) + 1;
const { data } = await this.getTodoItem(index);
this.setTodo(data);
}
},
mounted() {
this.getTodo();
}
};
</script>
<template>
<div id="app">
<div v-if="hasTodo">Todo: {{ todo.title }}</div>
<b v-else>Loading..</b>
</div>
</template>
https://codesandbox.io/s/vue-template-y69be

Related

Can't get v-model work with Composition API and Vuex

I've read several posts on stackoverflow and other websites, but still can't figure out what's going wrong in my case.
I'm building an app following composition api approach and using a variable called modelStartDate (which I initiate at Jan 3, 2022). This is how my store looks:
import { createStore } from 'vuex'
export default createStore({
state: {
modelStartDate: new Date(2022, 0, 3)
},
mutations: {
modelStartDateMutation(state, newDate) {
state.modelStartDate = newDate
}
},
actions: {
},
getters: {
},
modules: {
}
})
In the relevant Vue file, I have the following code snippet:
<template>
<nav class="left-bar">
<div class="block" id="modelStartDate">
<label>Model start date</label>
<input type="date" v-model="modelStartDateProxy" />
</div>
<p>{{ modelStartDate }}</p>
</nav>
</template>
<script>
import { ref } from '#vue/reactivity'
import { useStore } from 'vuex'
import { computed } from '#vue/runtime-core'
export default {
setup() {
const store = useStore()
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit("modelStartDateMutation", newDate)
})
const modelStartDate = store.state.modelStartDate
return { modelStartDateProxy, modelStartDate }
}
}
</script>
When I run the page, the paragraph tag prints the right date, however the input tag, where the user can change the date, is empty (I was expecting Jan 3, 2022 to be pre-selected). When the date is changed, nothing seems to change in the app. I'm getting no errors. Any idea what I'm doing incorrectly?
Also, can I access store's modelStartDate state without having to define it separately (redundantly?) in the vue setup() section?
First, I don't know which tutorial you read. But to me, the problem is here:
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit("modelStartDateMutation", newDate)
})
const modelStartDate = store.state.modelStartDate
The snippet
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit("modelStartDateMutation", newDate)
})
is weird to me.
Duplicate of store.state.modelStartDate. DRY.
<p>{{ modelStartDate }}</p> render data from const modelStartDate = store.state.modelStartDate. But the data was only assign once. So the new value was not render on input was changed.
Solution:
const modelStartDate = computed(() => store.state.modelStartDate);
You can take a look at this playground.
The html element input returns a string: "YYYY-MM-DD". Therefore you need the syntax new Date(value)
Take a look at this playground
<template>
<label>Model start date</label>
<input type="date" v-model="modelStartDateProxy" />
<p>{{ modelStartDateProxy }}</p>
</template>
<script>
import { store } from './store.js' //mock-up store
import { ref, computed } from 'vue'
export default {
setup() {
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit(newDate) // Use real Vuex syntax
})
return { modelStartDateProxy }
}
}
</script>
//Mock-up Store (not real vuex)
import {reactive} from 'vue'
export const store = reactive({
state: {
modelStartDate: new Date(2022, 0, 3)
},
commit: (value) => store.state.modelStartDate = new Date(value) // new Date(value)
})

Is there a way to share reactive data between random components in Vue 3 Composition API?

Having some reactive const in "Component A," which may update after some user action, how could this data be imported into another component?
For example:
const MyComponent = {
import { computed, ref } from "vue";
setup() {
name: "Component A",
setup() {
const foo = ref(null);
const updateFoo = computed(() => foo.value = "bar");
return { foo }
}
}
}
Could the updated value of 'foo' be used in another Component without using provide/inject?
I am pretty new in the Vue ecosystem; kind apologies if this is something obvious that I am missing here.
One of the best things about composition API is that we can create reusable logic and use that all across the App. You create a composable functions in which you can create the logic and then import that into the components where you want to use it. Not only does this make your component much cleaner but also your APP much more maintainable. Below is a simple example of counter to show how they can be used. You can find working demo here:
Create a composable function for counter:
import { ref, computed } from "vue";
const counter = ref(0);
export const getCounter = () => {
const incrementCounter = () => counter.value++;
const decrementCounter = () => counter.value--;
const counterPositiveOrNegitive = computed(() =>
counter.value >= 0 ? " Positive" : "Negitive"
);
return {
counter,
incrementCounter,
decrementCounter,
counterPositiveOrNegitive
};
};
Then you can import this function into your components and get the function or you want to use. Component to increment counter.
<template>
<div class="hello">
<h1>Component To Increment Counter</h1>
<button #click="incrementCounter">Increment</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "IncrementCounter",
setup() {
const { incrementCounter } = getCounter();
return { incrementCounter };
},
};
</script>
Component to decrement counter:
<template>
<div class="hello">
<h1>Component To Decrement Counter</h1>
<button #click="decrementCounter">Decrement</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "DecrementCounter",
setup() {
const { decrementCounter } = getCounter();
return { decrementCounter };
},
};
</script>
Then in the main component, you can show the counter value.
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div class="counters">
<IncrementCounter />
<DecrementCounter />
</div>
<h3>Main Component </h3>
<p>Counter: {{ counter }}</p>
<p>{{ counterPositiveOrNegitive }}</p>
</template>
<script>
import IncrementCounter from "./components/IncrementCounter.vue";
import DecrementCounter from "./components/DecrementCounter.vue";
import { getCounter } from "./composables/counterExample";
export default {
name: "App",
components: {
IncrementCounter: IncrementCounter,
DecrementCounter: DecrementCounter,
},
setup() {
const { counter, counterPositiveOrNegitive } = getCounter();
return { counter, counterPositiveOrNegitive };
},
};
Hope this was somewhat helpful. You can find a working example here:
https://codesandbox.io/s/vue3-composition-api-blfpj

How to use InfiniteLoading in vuex (error)

I'm a beginner to vuex and vue, I need to use a infinite-loading in vuex project, but it is not working, I need some experts' help. I will provide details as below:
my vuex code in index.js
import axios from 'axios';
const state = {
productItems_bottom: []
}
const mutations = {
UPDATE_PRODUCT_ITEMS_bottom (state, payload) {
state.productItems_bottom = payload;
}
}
const actions = {
getProductItems_bottom ({ commit },lastpage) {
axios.get('http://127.0.0.1:8000/api/standstop?page='+lastpage).then((response) => {
commit('UPDATE_PRODUCT_ITEMS_bottom', response.data)
});
}
}
const getters = {
productItems_bottom: state => state.productItems_bottom,
last_page: state=> state.productItems_bottom.last_page
}
const index_page_Bottom_Module = {
state,
mutations,
actions,
getters
}
export default index_page_Bottom_Module;
And my vue code display here, but something's wrong.
<template>
<section id="product-item_bottom" v-if="productItems_bottom">
<div class="columns is-multiline is-mobile">
<div v-for="productItem_bottom in productItems_bottom" :key="productItem_bottom.id" class="column is-one-third">
<figure class="image"><img v-bind:src="'http://localhost/plategea%20laravel%20and%20vue/plategea/plategea/public/storage/'+productItem_bottom.src">
<span class="tag is-primary">{{ productItem_bottom.title }}</span>
</figure>
</div>
</div>
<infinite-loading #distance="1" #infinite="infiniteHandler"/>
</section>
</template>
<script>
import InfiniteLoading from 'vue-infinite-loading';
import {mapGetters } from 'vuex';
let lastpage=1;
export default {
name:'ProductItem_bottom',
computed: {
...mapGetters(['productItems_bottom','last_page'])
},
created(){
},
methods:{
infiniteHandler($state) {
this.$store.dispatch('getProductItems_bottom',lastpage);
this.$store.commit({ type:'UPDATE_PRODUCT_ITEMS_bottom' });
console.log(this.last_page);
if (lastpage<=this.last_page) {
$state.loaded();
lastpage +=1;
}
if(lastpage > this.last_page){
$state.complete();
}
}
},
components: {
InfiniteLoading,
}
}
</script>
And my api
public function showStandstop()
{
$stands=Stands::where('state',1)->orderBy('trend', 'desc')->paginate(3);
return response()->json($stands, 200);
}
but InfiniteLoading not working! I don't know what is the problem & where should I look for, do you have any idea what shall I change & work on?
thank you in advance I'm looking forward to seeing your responds.
I think your not assigning the payload to the productItems_bottom.last_page anywhere.
UPDATE_PRODUCT_ITEMS_bottom (state, payload) {
state.productItems_bottom = payload;
}
last_page: state=> state.productItems_bottom.last_page
can you check that and let me know if is working...

How to re-use component that should use unique vuex store instance

I try to find a way to use vuex with reusable component which store data in a store. The thing is, I need the store to be unique for each component instance.
I thought Reusable module of the doc was the key but finally it doesn't seem to be for this purpose, or i didn't understand how to use it.
The parent component:
(the prop “req-path” is used to pass different URL to make each FileExplorer component commit the action of fetching data from an API, with that url path)
<template>
<div class="container">
<FileExplorer req-path="/folder/subfolder"></FileExplorer>
<FileExplorer req-path="/anotherfolder"></FileExplorer>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
import FileExplorer from "#/components/FileExplorer.vue";
export default {
components: {
FileExplorer
}
};
</script>
The reusable component:
<template>
<div class="container">
<ul v-for="(item, index) in folderIndex" :key="index">
<li>Results: {{ item.name }}</li>
</ul>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
props: ["reqPath"],
},
computed: {
...mapState("fileExplorer", ["folderIndex"])
},
created() {
// FETCH DATA FROM API
this.$store
.dispatch("fileExplorer/indexingData", {
reqPath: this.reqPath
})
.catch(error => {
console.log("An error occurred:", error);
this.errors = error.response.data.data;
});
}
};
</script>
store.js where I invoke my store module that I separate in different files, here only fileExplorer module interest us.
EDIT : I simplified the file for clarity purpose but I have some other state and many mutations inside.
import Vue from 'vue'
import Vuex from 'vuex'
// Import modules
import { fileExplorer } from '#/store/modules/fileExplorer'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
fileExplorer,
…
}
})
#/store/modules/fileExplorer.js
import ApiService from "#/utils/ApiService"
export const fileExplorer = ({
namespaced: true,
state: {
folderIndex: {},
},
mutations: {
// Called from action (indexingData) to fetch folder/fil structure from API
SET_FOLDERS_INDEX(state, data) {
state.folderIndex = data.indexingData
},
actions: {
// Fetch data from API using req-path as url
indexingData({
commit
}, reqPath) {
return ApiService.indexingData(reqPath)
.then((response) => {
commit('SET_FOLDERS_INDEX', response.data);
})
.catch((error) => {
console.log('There was an error:', error.response);
});
}
}
});
I need each component to show different data from those 2 different URL, instead i get the same data in the 2 component instance (not surprising though).
Thanks a lot for any of those who read all that !
Module reuse is about when you are creating multiple modules from the same module config.
First, use a function for declaring module state instead of a plain object.
If we use a plain object to declare the state of the module, then that
state object will be shared by reference and cause cross store/module
state pollution when it's mutated.
const fileExplorer = {
state () {
return {
folderIndex: {}
}
},
// mutations, actions, getters...
}
Then, dynamically register a new module each time a new FileExplorer component is created and unregister that module before the component is destroyed.
<template>
<div class="container">
<ul v-for="(item, index) in folderIndex" :key="index">
<li>Results: {{ item.name }}</li>
</ul>
</div>
</div>
</template>
<script>
import { fileExplorer } from "#/store/modules/fileExplorer";
import store from "#/store/index";
var uid = 1
export default {
props: ["reqPath"],
data() {
return {
namespace: `fileExplorer${uid++}`
}
},
computed: {
folderIndex() {
return this.$store.state[this.namespace].folderIndex
}
},
created() {
// Register the new module dynamically
store.registerModule(this.namespace, fileExplorer);
// FETCH DATA FROM API
this.$store
.dispatch(`${this.namespace}/indexingData`, {
reqPath: this.reqPath
})
.catch(error => {
console.log("An error occurred:", error);
this.errors = error.response.data.data;
});
},
beforeDestroy() {
// Unregister the dynamically created module
store.unregisterModule(this.namespace);
}
};
</script>
You no longer need the static module registration declared at store creation.
export default new Vuex.Store({
modules: {
// fileExplorer, <-- Remove this static module
}
})

vue, vuex, vue-i18n change language button event

Why changing mutation do not update page in new language?
main.js : here I implemented vue-i18n with vue:
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import VueI18n from 'vue-i18n'
import locales from './locales'
import router from './router'
import store from './store'
import App from './App'
Vue.use(VueRouter)
Vue.use(VueResource)
Vue.use(VueI18n, store)
Vue.http.interceptors.push((request, next) => {
console.log('sending request: ', request)
next(response => {
console.log('response: ', response)
})
})
Vue.config.debug = true
Vue.config.lang = 'fa'
Object.keys(locales).forEach(lang => {
Vue.locale(lang, locales[lang])
})
const app = new Vue({
el: '#app',
router,
VueI18n,
store,
render: h => h(App)
})
app.$mount('#app')
App.vue: Then used two buttons to change language:
<template>
<div id="app">
<h2>{{ $t('example', '#store.state.culture') }}</h2>
<p>{{ count }}</p>
<p>
<button #click="increment()">+</button>
<button #click="decrement()">-</button>
</p>
<p>culture: {{ culture }}</p>
<p>
<button #click=' changeCulture("en") '>English</button>
<button #click=' changeCulture("fa") '>پارسی</button>
</p>
<input type="text" v-model="newUserName">
<button #click="handleAddUserButton()">add</button>
<div>
<router-link to="/page1">Go to page1</router-link>
<router-link to="/page2">Go to page2</router-link>
</div>
<transition name="fade" mode="out-in">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
<img src="./assets/logo.png">
<hello></hello>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
// import App from './main.js'
import Hello from 'components/Hello'
export default {
name: 'app',
components: {
Hello
},
data () {
return {
newUserName: ''
}
},
computed: {
...mapGetters([
'count',
'culture'
])
},
methods: {
...mapActions([
'increment',
'decrement',
'exampleGetFirebaseData',
'examplePostFirebaseData',
'changeCulture'
]),
handleAddUserButton () {
const user = {
name: this.newUserName
}
this.examplePostFirebaseData(user)
.then(resp => {
// console.log('resp: ', resp)
})
.catch(error => {
console.log('catch error: ', error)
})
},
handleError () {
}
},
beforeMount () {
this.exampleGetFirebaseData()
.then(resp => {
// console.log('resp: ', resp)
})
.catch(error => {
this.handleError(error)
// console.log('catch error: ', error)
})
}
}
</script>
sotre > culture.js: Then using store, getters, actions and mutation to change langauge,
const state = {
locales: ['en', 'fa'],
culture: 'en'
}
const getters = {
culture: state => state.culture
}
const actions = {
async changeCulture ({ commit }, playload) {
commit('CHANGE', playload)
}
}
import App from '../../main.js'
const mutations = {
CHANGE (state, payload) {
if (state.locales.indexOf(payload) !== -1) {
state.culture = payload
} else state.culture = 'en'
console.log(App.i18n)
}
}
export default {
state,
getters,
actions,
mutations
}
I checked using vue development tools in chrome and culture is changed but the problem is that title of {{ $t("example")}} do not change as mutation change.
I know doubt something basic is wrong in my code, may you please help.
Many Thanks in advance.
Becasue this is not a valid expression for i18n
<h2>{{ $t('example', '#store.state.culture') }}</h2>
If you want to translate the text en and fa do this
<h2>{{ $t(culture) }}</h2>
OR
<h2>{{ $t(`namespace:${culture}`) }}</h2>
If you want to use namespace