How could I get changed value from vuetify select by emitting in atomic design pattern? - vuex

I am trying to get the changed value of vuetify v-select by using $emit but it doesn't work.
I divided components by applying atomic design pattern (atoms(child component and not to connect with the store), organisms(parent component)) and vuex stores.
I think $emit data is OK but anything doesn't work after the process.
This is for a new application for management page with
using vue, vuex, vuetify, atomic design connecting to API server.
Components
child component - in atoms folder
<template>
<v-select
:items="list"
:label="label"
v-model="selected"
item-value="id"
item-text="name"
return-object
#change="changeSelected"
></v-select>
</template>
<script>
export default {
props: ["list", "label", "defaultSelected"],
data() {
return {
selected: this.defaultSelected
};
},
methods: {
changeSelected(newValue) {
console.log(newValue); // display changed new data
this.$emit("changeSelected", newValue);
}
}
};
</script>
parent component - in organisms folder
<template>
<v-select-child
:select-label="label"
:select-list="list"
:default-selected="selected"
#change-selected="changeSelected" // problem issue?
>
</v-select-child>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
...
},
computed: {
...mapState({
list: state => state.list
})
},
methods: {
changeSelected() {
console.log("changeSelected"); // doesn't work
this.$store.dispatch("setSelected", { payload: this.selected });
}
}
};
</script>
vuex stores
index.js
export default new Vuex.Store({
modules: {
xxx
},
state: {
list: [
{
name: "aaaaa",
id: "001"
},
{
name: "bbbbb",
id: "002"
}
]
},
getters: {},
mutations: {},
actions: {}
});
xxx.js
export default {
selected: { id: "001" }
},
getters: {
//
},
mutations: {
updateSelected(state, payload) {
console.log("payload"); // doesn't work
console.log(payload);
state.selected = payload;
console.log(state.selected);
}
},
actions: {
setSelected({ commit }, payload) {
console.log("Action"); // doesn't work
commit("updateSelected", payload);
}
}
};
It does not print any console log after changeSelected function.

In document
Unlike components and props, event names don’t provide any automatic
case transformation. Instead, the name of an emitted event must
exactly match the name used to listen to that event.
That means if you emit event like $emit('changeSelected'), then you need to use #changeSelected. #change-selected will not work.
<v-select-child
:select-label="label"
:select-list="list"
:default-selected="selected"
#changeSelected="changeSelected"
>
</v-select-child>

I found a solution below:
child component
<template>
<v-select
:label="label"
:items="list"
v-model="selected"
item-value="id"
item-text="name"
return-object
></v-select>
</template>
<script>
export default {
props: ["list", "label", "defaultSelected"],
computed: {
selected: {
get() {
return this.defaultSelected;
},
set(newVal) {
if (this.selected !== newVal) {
this.$emit("changeSelected", newVal);
}
}
}
}
};
</script>
parent component
<template>
<v-select-child
:label="label"
:list="list"
:defaultSelected="selected"
#changeSelected="changeSelected" // fix the property using camelCase
></v-select-child>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
...
},
computed: {
...mapState({
list: state => state.list
})
},
methods: {
changeSelected(val) { // val: changed object value
this.$store.dispatch("setSelected", { id:val.id });
}
}
};
</script>

You can also use watch;
<v-select
:label="label"
:items="list"
v-model="selected"
item-value="id"
item-text="name"
></v-select>
</template>
...
watch:{
selected(){
this.$emit('changeValue', this.selected.id');
}
}
...
and from parent;
<child #changeValue="id = $event" .. />

Related

Vue received a Component which was made a reactive object

The problem I need to solve: I am writing a little vue-app based on VueJS3.
I got a lot of different sidebars and I need to prevent the case that more than one sidebar is open at the very same time.
To archive this I am following this article.
Now I got a problem:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. (6)
This is my code:
SlideOvers.vue
<template>
<component :is="component" :component="component" v-if="open"/>
</template>
<script>
export default {
name: 'SlideOvers',
computed: {
component() {
return this.$store.state.slideovers.sidebarComponent
},
open () {
return this.$store.state.slideovers.sidebarOpen
},
},
}
</script>
UserSlideOver.vue
<template>
<div>test</div>
</template>
<script>
export default {
name: 'UserSlideOver',
components: {},
computed: {
open () {
return this.$store.state.slideovers.sidebarOpen
},
component () {
return this.$store.state.slideovers.sidebarComponent
}
},
}
</script>
slideovers.js (vuex-store)
import * as types from '../mutation-types'
const state = {
sidebarOpen: false,
sidebarComponent: null
}
const getters = {
sidebarOpen: state => state.sidebarOpen,
sidebarComponent: state => state.sidebarComponent
}
const actions = {
toggleSidebar ({commit, state}, component) {
commit (types.TOGGLE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
},
closeSidebar ({commit, state}, component) {
commit (types.CLOSE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
}
}
const mutations = {
[types.TOGGLE_SIDEBAR] (state) {
state.sidebarOpen = !state.sidebarOpen
},
[types.CLOSE_SIDEBAR] (state) {
state.sidebarOpen = false
},
[types.SET_SIDEBAR_COMPONENT] (state, component) {
state.sidebarComponent = component
}
}
export default {
state,
getters,
actions,
mutations
}
App.vue
<template>
<SlideOvers/>
<router-view ref="routerView"/>
</template>
<script>
import SlideOvers from "./SlideOvers";
export default {
name: 'app',
components: {SlideOvers},
};
</script>
And this is how I try to toggle one slideover:
<template>
<router-link
v-slot="{ href, navigate }"
to="/">
<a :href="href"
#click="$store.dispatch ('toggleSidebar', userslideover)">
Test
</a>
</router-link>
</template>
<script>
import {defineAsyncComponent} from "vue";
export default {
components: {
},
data() {
return {
userslideover: defineAsyncComponent(() =>
import('../../UserSlideOver')
),
};
},
};
</script>
Following the recommendation of the warning, use markRaw on the value of usersslideover to resolve the warning:
export default {
data() {
return {
userslideover: markRaw(defineAsyncComponent(() => import('../../UserSlideOver.vue') )),
}
}
}
demo
You can use Object.freeze to get rid of the warning.
If you only use shallowRef f.e., the component will only be mounted once and is not usable in a dynamic component.
<script setup>
import InputField from "src/core/components/InputField.vue";
const inputField = Object.freeze(InputField);
const reactiveComponent = ref(undefined);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = undefined;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
</script>
<template>
<component :is="reactiveComponent" />
</template>

Vuex computed property not updating

I am having a hard time trying to update my component state based off a reference Id that I am using to display a result that lives in my store/vuex. Basically, what I want to happen is for when I update a reference ID, my getters/computed passes down the updates value. I have provided a simple version of what I am trying to do with no luck. Any ideas?
What am I doing wrong?
Parent Component file
<template>
<div class="parent"
<Car :car="car" />
<div class="car-btn" #click="switchCarId('11')" >Change Car</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import Car from './Car.vue';
export default {
name: 'Car',
components: { Car },
computed: {
...mapGetters(['car'])
},
methods: {
switchCarId(val) {
this.$store.commit('updateCarReferenceId', val);
}
}
};
</script>
Child Component file
<template>
<div class="car">{{ car.name }} </div>
</template>
<script>
export default {
name: 'Car',
props: { 'car': Object }
};
</script>
Store file
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
selectedCarId: '10',
cars: {
'10': {
name: 'Red Car'
},
'11': {
name: 'Blue Car'
}
}
},
mutations: {
updateCarReferenceId(state, val) {
state.selectedCarId = val;
}
},
getters: {
car: (state, getters) => {
return getters.cars[getters.getSelectedCarId];
},
cars: (state) => {
return state.cars;
},
getSelectedCarId: (state) => {
return state.selectedCarId;
}
}
});
I solved this issue. Turns out deep down in a component I was change changing the value based on a input. So no matter what the state read, it only read it initially. Thanks to Anoatoly for helping me run through it and making sure I dotted my t's and crossing my i's.

How do I trigger an AJAX request when props is changed?

I have a this App component
<template>
<div id="app">
<Component1 #addItem="addItem" />
<Component2 :items="items" />
</div>
</template>
<script>
import Component1 from './components/Component1'
import Component2 from './components/Component2'
export default {
name: 'app',
components: { Component1, Component2 },
data: function () {
return {
items: [],
}
},
methods: function () {
addItem(item) {
items.push(item)
},
},
}
</script>
This is my Component2 component:
<template>
<div>
{{ ajax_data }}
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Component2',
props: ['items'],
data: function () {
return {
ajax_data: null
}
},
mounted () {
this.callAJAX()
},
methods: {
callAJAX() {
axios
.get('/api/get-some-data', {
params: {
items: items
}
})
.then((response) => {
this.ajax_data = response.data
})
},
},
}
</script>
I want to trigger the AJAX everytime I add an item. The problem with my code is since Component2 is already mounted and when an item is added the AJAX is not running. So then I added this hook:
updated () {
this.callAJAX()
},
The problem with this is its running an infinite loop.
Is there a proper way to do this?
You can simply detect if value change with a watcher
https://v2.vuejs.org/v2/guide/computed.html#Watchers
https://v2.vuejs.org/v2/api/#watch
in your case, you may set the deep property to true...

VueJS set custom component default value from props

Hi guys I tried to create a VueJS custom component to wrap Vue Autonumeric component.
https://github.com/autoNumeric/vue-autoNumeric
In Vue Autonumeric page it specifically mention the caveat
Caveats Please note that directly setting a :value='42' on the
component will break it (really!). Do NOT do that:
So in my custom component MoneyComponent.vue, I create a v-model
This is the full code
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
methods: {
},
watch: {
amount (value) {
this.$emit('input', value);
}
},
}
</script>
Usage example
<template>
<v-money
v-model="price"
></v-money>
</template>
<script>
export default {
data() {
return {
price: 45,
}
},
methods: {
}
}
<script>
This works on on initial value from parent. However if I change the price property to 55 for example, the amount property in MoneyComponent is not changing.
What is the problem here the amount property is not reactive on second changes? How do I fix it?
Thanks
Because you're using v-model, you need to emit input event to make data changed in the parent
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
then your component
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
}
</script>

Changing a vuex state from a different component?

I have a component (modal) which relies on a store. The store has the state of the modal component - whether it is active or not.
I need to be able to call this modal to open from other components or even just on a standard link. It opens by adding an .active class.
How can I change the state of the store - either by calling the stores action or calling the modal components method (which is mapped to the store).
Modal Store:
class ModalModule {
constructor() {
return {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
};
}
}
export default ModalModule;
Vue Component:
<template>
<div class="modal" v-bind:class="{ active: active }">
<div class="modal-inner">
<h1>modal content here</h1>
</div>
<div class="modal-close" v-on:click="this.toggleActive">
X
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
computed: {
...mapGetters('Modal', [
'active',
])
},
methods: {
...mapActions('Modal', [
'toggleActive',
]),
}
}
</script>
And somewhere else I want to be able to have something like:
<button v-on:click="how to change the state??">OPEN MODAL</button>
Edit:
Here's the store:
import Vuex from 'vuex';
import ModalModule from './ModalModule';
class Store extends Vuex.Store {
constructor() {
Vue.use(Vuex);
super({
modules: {
Modal: new ModalModule(),
}
});
};
}
You do not need an action for your particular usecase . You just just define a mutation as you are just changing the boolean value of a property in a state. Actions are for async functionality. You usecase is just synchronous change of Boolean value
So you can do
<button v-on:click="$store.commit('toggleActive')">OPEN MODAL</button>
EDIT:
Just export a plain object
const ModalModule = {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
}
export default ModalModule;// export the module
Even remove the class based definition of the store
import Vue from 'vue'
import Vuex from 'vuex';
import ModalModule from './ModalModule';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
ModalModule
}
});
And change it like this in you component for mapping of the mutation (<MODULE_NAME>/<MUTATION_NAME>)
...mapMutations([
'ModalModule/toggleActive'
])
You can access the store from your components via this.$store. There you can call your actions and mutations. So
<button v-on:click="$store.commit('your mutation name', true)">OPEN MODAL</button>