I would like to set properties to buttons that I use in various parts of my project, and therefore I cannot insert static text in each button, but I have tried to use the state of vuex so that if I have to change the name it I change only in the state for all buttons, instead of going to change it by looking for it in every single button.
This solution does not work for me because nothing appears in the buttons, instead the words "Foo" and "Bar" should appear (indeed I would like them to appear to me). In practice it does not take the btnType property.
This is one of my components:
<template>
<div>
<b-button
v-for="(btn, idx) in buttons"
:key="idx"
:class="btn.class"
variant="info"
:name="btn.btnType"
>{{ btn.btnType }}</b-button
>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
computed: {
...mapState({
buttonFoo: "buttonFoo",
buttonBar: "buttonBar",
}),
},
data() {
return {
buttons: [
{
btnType: this.buttonFoo,
state: true,
class: "button-class1",
},
{
btnType: this.buttonBar,
state: false,
class: "button-class2",
},
],
};
},
};
</script>
And this is my index file:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
buttonFoo: "Foo",
buttonBar: "Bar"
},
mutations: {},
etc...
});
data evaluates before computed property, hence can not access computed property in data.
better to do it in mounted
export default {
computed: {
...mapState({
buttonFoo: "buttonFoo",
buttonBar: "buttonBar",
}),
},
data() {
return {
buttons: []
};
},
mounted(){
this.buttons=[
{
btnType: this.buttonFoo,
state: true,
class: "button-class1",
},
{
btnType: this.buttonBar,
state: false,
class: "button-class2",
},
],
}
};
Related
I'm building a web app using Nuxt v2.15 and #nuxtjs/i18n v7.2. I'm using Vuex for state management. In my global state, I want to create a getter that returns a value based on this.$i18n.locale.
What is the best way to access the current app context, which has $i18n attached to it, in my Vuex getter method?
nuxt.config.js
export default {
modules: [
"#nuxtjs/i18n", // https://i18n.nuxtjs.org/
],
i18n: {
locales: { /* configuration */ },
defaultLocale: "en",
detectBrowserLanguage: {
fallbackLocale: "en",
redirectOn: "root",
},
langDir: "~/locales/",
vueI18n: {
fallbackLocale: "en",
messages: { /* translation data */ },
},
},
};
store/index.js
export const state = () => ({
// Root module for Vuex store: https://nuxtjs.org/docs/directory-structure/store/
});
export const getters = {
loginOpts() {
const locale = this.$i18n.locale;
const uiLocales = [locale];
if (locale === "zh") {
uiLocales.push("zh-CN");
}
return { params: { ui_locales: uiLocales } };
},
};
components/login.vue
<template>
<div>
<v-btn v-if="$auth.loggedIn" #click="$auth.logout()">Sign Out</v-btn>
<v-btn v-else #click="$auth.loginWith('auth0', $store.getters.loginOpts)">Sign In</v-btn>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Login",
data() {
return {};
},
computed: {
...mapGetters(["loginOpts"]),
},
};
</script>
What I expect
I expect to be able to access this.$i18n from a Vuex getter, or to have some means of doing so.
What actually happens
In the getter method, this is undefined.
TypeError: Cannot read properties of undefined (reading '$i18n')
What I've tried
I see here that a getter is passed four arguments: state, getters, rootState, and rootGetters.
I've tried:
RTFM for Vuex State, Getters, and Nuxt i18n
accessing state.$i18n
adding $i18n: this.$i18n to the root state
I had a similar problem last week where I absolutely needed to use i18n inside a vuex getter.
I wouldn't recommend this as its probably not the most performant thing in the world but it worked for me and solved a huge problem while I was working on a government application.
components/login.vue
<template>
<div>
<v-btn v-if="$auth.loggedIn" #click="$auth.logout()">Sign Out</v-btn>
<v-btn v-else #click="$auth.loginWith('auth0', theLoginOpts)">Sign In</v-btn>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Login",
data() {
return {
theLoginOpts: $store.getters.loginOpts(this) // The secret sauce
};
},
computed: {
...mapGetters(["loginOpts"]),
},
};
</script>
store/index.js
export const state = () => ({
// Root module for Vuex store: https://nuxtjs.org/docs/directory-structure/store/
});
export const getters = {
loginOpts: (state) => (app) => { // App is the secret sauce from earlier
const locale = app.$i18n.locale;
const uiLocales = [locale];
if (locale === "zh") {
uiLocales.push("zh-CN");
}
return { params: { ui_locales: uiLocales } };
},
};
After digging through docs, experimenting, and discussing with the folks who were kind enough to offer answers here, it is clear that i18n is not directly accessible in a Vuex getter method, despite the fact that #nuxt/i18n registers a Vuex module called i18n and everything I've read about Vuex modules suggests this should be possible.
I did however come across the docs for #nuxt/i18n callbacks, which led me to create a small plugin that sets the value of locale and uiLocales in the global state using a mutation.
The end result looks like this:
nuxt.config.js
export default {
modules: [
"#nuxtjs/i18n", // https://i18n.nuxtjs.org/
],
plugins: [
"~/plugins/i18n.js",
],
i18n: {
locales: { /* configuration */ },
defaultLocale: "en",
detectBrowserLanguage: {
fallbackLocale: "en",
redirectOn: "root",
},
langDir: "~/locales/",
vueI18n: {
fallbackLocale: "en",
messages: { /* translation data */ },
},
},
};
plugins/i18n.js
export function findLocaleConfig (i18n, locale) {
return (
i18n.locales.find(({ iso, code }) => [iso, code].includes(locale)) || {}
);
}
export default function ({ app }) {
app.store.commit("localeChange", findLocaleConfig(app.i18n, app.i18n.locale));
app.i18n.onLanguageSwitched = (oldLocale, newLocale) => {
app.store.commit("localeChange", findLocaleConfig(app.i18n, newLocale));
};
}
store/index.js
export const state = () => ({
locale: null,
uiLocales: [],
});
export const mutations = {
localeChange(state, locale) {
state.locale = locale.code;
state.uiLocales = [locale.code, locale.iso];
},
};
components/login.vue
<template>
<div>
<v-btn v-if="$auth.loggedIn" #click="$auth.logout()">Sign Out</v-btn>
<v-btn v-else #click="$auth.loginWith('auth0', loginOpts)">Sign In</v-btn>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {};
},
computed: {
loginOpts() {
const uiLocales = this.$store.state.uiLocales;
return { params: { ui_locales: uiLocales } };
},
},
};
</script>
You can access a the locale in an Vuex action with: this.app.i18n.locale.
This cannot be accessed in a state or getter (a getter is a not a place for it anyway IMO).
PS: The above means that means that you can access this value anywhere you have access to the Nuxt context.
The simple way we did in our project is like below:
In component computed prop
teamsForSelector() {
return this.$store.getters['company/teamsForSelector'](this.$i18n);
}
and in state getters
teamsForSelector: (state) => ($i18n) => {
const teamsForSelector = state.teams.map((team) => {
return {
id: team.teamUid,
label: team.teamName,
};
});
teamsForSelector.unshift({
id: 'all-teams',
label: $i18n.t('key'),
});
return teamsForSelector;
}
Do you know how to change a component dynamically with object prop
App.vue
<template>
<div id="app">
<component :is="current['test'].target.name"> </component>
<input type="button" value="click me" #click="change" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
import Comp from "./components/Comp.vue";
export default {
name: "App",
components: {
HelloWorld,
Comp,
},
data() {
return {
current: {},
};
},
created() {
this.current["test"] = {
index: 0,
target: {
name: "Comp",
},
};
},
methods: {
change() {
const r =
this.current["test"].target.name === "HelloWorld"
? "Comp"
: "HelloWorld";
this.current["test"].target = {
name: r,
};
console.log(this.current["test"]);
},
},
};
</script>
Comp.vue
<template>
<p>Template 2</p>
</template>
HelloWorld.vue
<template>
<p>Template 1</p>
</template>
https://codesandbox.io/s/clever-water-dgbts?file=/src/components/HelloWorld.vue:0-42
The value of the object will change correctly but not the component.
Thank you
The issue here is that the property test is not defined on the object current in the data definition - you're setting the definition in the created() function. This means that Vue does not know to create the reactive getter/setter for that property.
Change your data definition to:
data() {
return {
current: {
test: {
index: 0,
target: {
name: "Comp"
}
}
}
};
}
It is because of the way Vue does its reactivity (requiring pre-defined properties) that I would recommend steering clear of accessing properties as dictionary items i.e. use:
current.test.target.name
instead of
current['test'].target.name
For more information on Vue reactivity see this page: link
I'm new with VueJS, and I'm creating a VueJS app where you can get some informations about a Github User,
(example: https://api.github.com/users/versifiction)
I created a store with VueX, but I need to update the value written by the user in the input,
My "inputValue" is always at "" (its default value) and when I type inside the input, the store value still at ""
I tried this :
Input.vue
<template>
<div class="input">
<input
type="text"
:placeholder="placeholder"
v-model="inputValue"
#change="setInputValue(inputValue)"
#keyup.enter="getResult(inputValue)"
/>
<input type="submit" #click="getResult(inputValue)" />
</div>
</template>
<script>
import store from "../store";
export default {
name: "Input",
props: {
placeholder: String,
},
computed: {
inputValue: () => store.state.inputValue,
},
methods: {
setInputValue: (payload) => {
store.commit("setInputValue", payload);
}
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
and this :
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
inputValue: "",
},
getters: {
getInputValue(state) {
return state.inputValue;
}
},
mutations: {
setInputValue(state, payload) {
console.log("setInputValue");
console.log("payload ", payload);
state.inputValue = payload;
},
},
});
According to the vuex docs in the form handling section you should do :
:value="inputValue"
#change="setInputValue"
and
methods: {
setInputValue: (event) => {
store.commit("setInputValue", event.target.value);
}
}
The simplest and elegant way to bind vuex and a component would be to use computed properties.
The above code would become,
<input
type="text"
:placeholder="placeholder"
v-model="inputValue"
#keyup.enter="getResult(inputValue)"
/>
and inside your computed properties, you'll need to replace inputValue with following code.
computed: {
inputValue: {
set(val){
this.$store.commit(‘mutationName’, val)
},
get() {
return this.$store.stateName
}
}
}
The error appears when I use a v-for loop to go through the 'allPosts' data on my div.
The Nuxt documentation says 'Modules: every .js file inside the store directory is transformed as a namespaced module'. Maybe I'm missing something in this regard?
pages/index.vue
<template>
<section id="postgrid">
<div v-for="post in allPosts" :key="post.id"></div>
</section>
</template>
<script>
import {mapGetters} from 'vuex'
import PostTile from '#/components/Blog/PostTile'
export default {
components: {
PostTile
},
computed: mapGetters(['allPosts'])
}
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import Posts from './posts'
const store = new Vuex.Store({
modules: {
Posts
}
})
store/posts.js
const state = () => ({
posts: [
{
id: 0,
title: 'A new beginning',
previewText: 'This will be awesome don\'t miss it',
category: 'Food',
featured_image: 'http://getwallpapers.com/wallpaper/full/6/9/8/668959.jpg',
slug: 'a-new-beginning',
post_body: '<p>Post body here</p>',
next_post_slug: 'a-second-beginning'
},
{
id: 1,
title: 'A second beginning',
previewText: 'This will be awesome don\'t miss it',
category: 'Venues',
featured_image: 'https://images.wallpaperscraft.com/image/beautiful_scenery_mountains_lake_nature_93318_1920x1080.jpg',
slug: 'a-second-beginning',
post_body: '<p>Post body here</p>',
prev_post_slug: 'a-new-beginning',
next_post_slug: 'a-third-beginning'
},
{
id: 2,
title: 'A third beginning',
previewText: 'This will be awesome don\'t miss it',
category: 'Experiences',
featured_image: 'http://eskipaper.com/images/beautiful-reflective-wallpaper-1.jpg',
slug: 'a-third-beginning',
post_body: '<p>Post body here</p>',
prev_post_slug: 'a-second-beginning',
next_post_slug: 'a-forth-beginning'
}
]
})
const getters = {
allPosts: (state) => state.posts
}
export default {
state,
getters
}
You have a number of issues in how you are setting up and accessing your store. Firstly you are creating your store using the "classic mode" which the docs tell us:
This feature is deprecated and will be removed in Nuxt 3.
So in order to be using the latest methods your store/index.js should look like this:
//store/index.js
//end
This is not a mistake, you don't actually need anything in it, just have it exist. There is no need to import vue or vuex or any modules.
Your store/posts.js can largely stay as it is, just change your state, mutations, getters, and actions to be exported constants and delete the bottom export:
//store/posts.js
export const state = () => ({
posts: [
...
]
})
export const mutations = {
}
export const actions = {
}
export const getters = {
allPosts: state => state.posts
}
//delete the following
export default {
state,
getters
}
Secondly you seem to be using mapGetters incorrectly. If you set up your store like I have above, you can use it in pages/index.vue like so:
//pages.index.vue
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters ({
allposts: 'posts/allPosts'
})
}
}
</script>
Then you can access "allPosts" in your template as you would any computed property or access it with "this.allPosts" in your script.
I'm getting started with Vue, I need to create a form of tiered select fields. That is the selected option for A, uses that to call the API to get the options for B, which determines options for C.
I'm still pretty new to frontend frameworks so this might be a terrible design. However not every inclusion of A (SelectState.vue) in a view requires all the children so making them modular was my first thought.
Currently I have a top level component that displays the select options:
SelectState.vue
<template>
<div id="select-state">
<span>{{ label }}</span>
<select v-model="selectedState">
<option v-for="state in states" :key="state">
{{ state }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'select-state',
data: function () {
return {
selectedState: '',
states: ['TX']
}
},
props: ['label']
// this.states = axios.get('xxx')
}
</script>
Index.vue
<template>
<div id="form">
<v-select-state label="State"></v-select-state>
<v-select-zip label="Zip"></v-select-zip>
</div>
</template>
<script>
import SelectState from './SelectState.vue'
import SelectZip from './SelectZip.vue'
export default {
name: 'Index',
components: {
'v-select-state': SelectState,
'v-select-Zip': SelectZip
}
}
</script>
Then I have a SelectZip.vue that is identical to SelectState.vue except that it has a parameter for its axios.get('XXX', params = {'state': ???}). But I'm stuck on how to "pass" that necessary parameter.
Thanks in advance!
edit: In conjunction with #dziraf's answer my working although verbose SelectedZip.vue is as follows:
<template>
<div id="select_zip">
<span>{{ label }}</span>
<select v-model="selected_zip">
<option v-for="zip in zips" :key="zip">
{{ zip }}
</option>
</select>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'select_zip',
data: function () {
return {
zips: []
}
},
props: ['label'],
computed: {
selected_zip: {
get () { return this.$store.state.formModule.zip },
set (value) { this.$store.commit('formModule/setZips', value) }
},
selected_state: {
get () { return this.$store.state.formModule.state }
}
},
methods: {
getValidZips (state) {
axios.post('/api/v1/get_valid_zips', {
params:{'state': state }})
.then(response => {
this.zips = response.data
})
.catch(error => {
console.log(error)
})
}
},
watch: {
selected_state (value) {
this.getValidZips(value)
}
}
}
</script>
You can pass it by adding 'state' props to your select components from your main form component, but I think it isn't a good long-term solution.
Instead, consider using Vuex. An example configuration would look like this:
#/store/modules/form.js
const Form = {
namespaced: true,
state: {
state: '',
zip: ''
},
getters: {},
mutations: {
setState (state, payload) {
state.state = payload
},
setZip (state, payload) {
state.zip = payload
}
},
actions: {}
}
export default Form
#/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import Form from './modules/form'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
formModule: Form,
}
})
export default store
#/main.js
// your impots
import store from './store/index'
// your configs
new Vue({
el: '#app',
router,
store, // add store to your main Vue instance so it's accessible with this.$store
axios,
components: { App },
template: '<App/>'
});
This would be your SelectState.vue:
<template>
<div id="select-state">
<span>{{ label }}</span>
<select v-model="selectedState">
<option v-for="state in states" :key="state">
{{ state }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'select-state',
data: function () {
return {
states: ['TX']
}
},
computed: {
selectedState: {
get() { return this.$store.state.formModule.state },
set(value) { this.$store.commit('formModule/setState', value) }
}
},
props: ['label']
}
</script>
Your SelectZip.vue would be the same, except you would instead use your store's zip as your v-model.
Your store variables are accessible across your app and you can access them with computed properties in your components.