Proxying data through getters to computed properties (v-model) - javascript

There is the following code when working with v-model and html with <input type ="text"> tags:
<template>
<InputTextApp class="inputTextAdditionData" placeholder_text="" v-model="cell_phone_number">
</InputTextApp>
</template>
<script>
import InputTextApp from '~/components/FormElements/InputTextApp';
export default{
data () {
return {
loading_cell_phone_number: '',
}
},
computed: {
cell_phone_number: {
get () {
return this.loading_cell_phone_number;
},
set (value) {
this.loading_cell_phone_number = value;
}
},
}
</script>
Question:
If in the content of calculated properties it is necessary to have the above code, how should I proxy the data from getters the what would it is code working fine?
As a primitive test, I tried to do something like this:
// vuex storage: tracker.js
const axios = require("axios");
export const getters = {
personTypeInput3: (state) => {
return {index: {
get () {
return this.loading_cell_phone_number;
},
set (value) {
this.loading_cell_phone_number = value;
}
},
}}
};
<template>
<InputTextApp class="inputTextAdditionData" placeholder_text="" v-model="cell_phone_number">
</InputTextApp>
</template>
<script>
import InputTextApp from '~/components/FormElements/InputTextApp';
export default{
data () {
return {
loading_cell_phone_number: '',
}
},
computed: {
cell_phone_number: {
...mapGetters("tracker", [
"personTypeInput3",
])
}
</script>
Then I accepted the content code in a computed property of the following form:
What needs to be written in the storage getter in order to get the code specified in the very first implementation (at the beginning of the post) in the computed property at the output?
(something like this:)

Related

Vue 3, call $emit on variable change

So I'm trying to build a component in Vue 3 that acts as a form, and in order for the data to be processed by the parent I want it to emit an object with all the inputs on change. The issue I'm having is that I don't seem to be able to call $emit from within watch() (probably because of the context, but I also don't see why the component-wide context isn't passed by default, and it doesn't accept this). I also cannot call any method because of the same reason.
I do see some people using the watch: {} syntax but as I understand it that is deprecated and it doesn't make a whole lot of sense to me either.
Here's a minimal example of what I'm trying to accomplish. Whenever the input date is changed, I want the component to emit a custom event.
<template>
<input
v-model="date"
name="dateInput"
type="date"
>
</template>
<script>
import { watch, ref } from "vue";
export default {
name: 'Demo',
props: {
},
emits: ["date-updated"],
setup() {
const date = ref("")
watch([date], (newVal) => {
// $emit is undefined
console.log(newVal);
$emit("date-updated", {newVal})
// watchHandler is undefined
watchHandler(newVal)
})
return {
date
}
},
data() {
return {
}
},
mounted() {
},
methods: {
watchHandler(newVal) {
console.log(newVal);
$emit("date-updated", {newVal})
}
},
}
</script>
Don't mix between option and composition api in order to keep the component consistent, the emit function is available in the context parameter of the setup hook::
<template>
<input
v-model="date"
name="dateInput"
type="date"
>
</template>
<script>
import { watch, ref } from "vue";
export default {
name: 'Demo',
props: {},
emits: ["date-updated"],
setup(props,context) {// or setup(props,{emit}) then use emit directly
const date = ref("")
watch(date, (newVal) => {
context.emit("date-updated", {newVal})
})
return {
date
}
},
}
</script>
if you want to add the method watchHandler you could define it a plain js function like :
...
watch(date, (newVal) => {
context.emit("date-updated", {newVal})
})
function watchHandler(newVal) {
console.log(newVal);
context.emit("date-updated", {newVal})
}
...

Replace a computed getter/setter with mapState and mapMutations

So, I am syncing a computed value to a component and setting it with a computed setter when it syncs back from the component.
My question is: Is it possible to replace a computed getter/setter with mapState and mapMutations or how would one achieve this in a more compact way?
<template>
<SomeComponent :value.sync="globalSuccess"></SomeComponent>
</template>
export default {
//...
computed: {
globalSuccess: {
get() {
return this.$store.state.globalSuccess;
},
set(val) {
this.$store.commit("globalSuccess", val);
}
}
}
}
I tried replacing it like this:
export default {
//...
computed: {
...mapState(["globalSuccess"]),
...mapMutations(["globalSuccess"]),
}
}
But unfortunately mapMutations(["globalSuccess"]) maps this.globalSuccess(value) to this.$store.commit('globalSuccess', value) according to the documentation of vuex.
But since my computed value gets set with globalSuccess = true internally through :value.sync in the template and not this.globalSuccess(true), globalSuccess will never be set to true.
Any idea how this could be possible? Or am I stuck using computed values with getter and setter?
So I just found out about this vuex module https://github.com/maoberlehner/vuex-map-fields which I installed as described on there:
// store.js
import { getField, updateField } from 'vuex-map-fields';
Vue.use(Vuex);
export default new Vuex.Store({
getters: {
getField,
//...
},
mutations: {
updateField,
//...
},
});
And then I made use of mapFields function:
// App.vue
export default {
//...
computed: {
...mapFields(["globalSuccess"]),
}
}
Which apparently dynamically maps to a computed setter and getter exactly as I wanted it:
export default {
//...
computed: {
globalSuccess: {
get() {
return this.$store.state.globalSuccess;
},
set(val) {
this.$store.commit("globalSuccess", val);
}
}
}
}
Here's a syntax I use:
export default {
//...
computed: {
globalSuccess: {
...mapState({ get: 'globalSuccess' }),
...mapMutations({ set: 'globalSuccess' }),
},
},
}
No additional dependencies needed. If you use it a lot, I suppose you could create a helper for it, but it is pretty neat as it is.

How can I reactively update a value in a component from a store value?

I have two components and a basic store as per the docs here: https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch.
I want to make it so that when I type into an input the value in a different component is updated by using the store.
Basic example here.
App.vue
<template>
<div id="app">
<h1>Store Demo</h1>
<BaseInputText /> Value From Store: {{ test }}
</div>
</template>
<script>
import BaseInputText from "./components/BaseInputText.vue";
import { store } from "../store.js";
export default {
// This should reactively changed as per the input
computed: {
test: function() {
return store.state.test;
}
},
components: {
BaseInputText
}
};
</script>
BaseInput.vue
<template>
<input type="text" class="input" v-model="test" />
</template>
<script>
import { store } from "../store.js";
export default {
data() {
return {
test: store.state.test
};
},
// When the value changes update the store
watch: {
test: function(newValue) {
store.setTest(newValue);
}
}
};
</script>
store.js
export const store = {
debug: true,
state: {
test: "hi"
},
setTest(newValue) {
if (this.debug) console.log("Set the test field with:", newValue);
this.state.test = newValue;
}
};
I want to make it so that when I type a string into the input the test variable in App.vue is updated. I'm trying to understand how the store pattern works. I'm aware of how to use props.
I also have a working copy here: https://codesandbox.io/s/loz79jnoq?fontsize=14
Updated
2.6.0+
To make store reactive use Vue.observable (added in in 2.6.0+)
store.js
import Vue from 'vue'
export const store = Vue.observable({
debug: true,
state: {
test: 'hi'
}
})
BaseInputText.vue
<input type="text" class="input" v-model="state.test">
...
data() {
return {
state: store.state
};
},
before 2.6.0
store.js
import Vue from 'vue'
export const store = new Vue({
data: {
debug: true,
state: {
test: 'hi'
}
}
})
BaseInputText.vue
<input type="text" class="input" v-model="state.test">
...
data() {
return {
state: store.state
};
}
Old answer
From documentation However, the difference is that computed properties are cached based on their reactive dependencies.
The store is not reactive
Change to
App.vue
data() {
return {
state: store.state
};
},
computed: {
test: function() {
return this.state.test;
}
},
It looks bad but I don't see another way to make it work

Vue: How to use component prop inside mapFields

I have general component and vuex store. For easy two-way binding I use vuex-map-fields. On component side it has mapFields method which creates get&set with mutations.
I want to pass namespace from vuex module with props but it seems to be impossible.
<my-component namespace="ns1" />
// my-component code
export default {
props: ["namespace"],
computed: {
...mapFields(??this.namespace??, ["attr1", "attr2"])
}
}
Of course, there is no way to use this in such way so we don't have access to props. How can I specify namespace as prop in such case?
The problem (as you probably gathered) is that computed properties are constructed before this is available, but you can get around it by deferring resolution of the this.namespace property until the computed property is called (which won't happen until component construction is finished).
The concept is based on this post Generating computed properties on the fly.
The basic pattern is to use a computed with get() and set()
computed: {
foo: {
get() { this.namespace...},
set() { this.namespace...},
}
}
but rather than type it all out in the component we can create a helper function based on the vuex-map-fields mapFields() function (see here for the original).
The normalizeNamespace() function that comes with vuex-map-fields does not support what we want to do, so we drop it and assume the namespace is always passed in (and that the store module uses the standard getField and updateField functions).
I have adapted one of the vuex-map-fields Codesandbox examples here.
Note the namespace is in data rather than props for conveniance, but props should work also.
Template
<template>
<div id="app">
<div>
<label>foo </label> <input v-model="foo" /> <span> {{ foo }}</span>
</div>
<br />
<div>
<label>bar </label> <input v-model="bar" /> <span> {{ bar }}</span>
</div>
</div>
</template>
Helper
<script>
const mapFields2 = (namespaceProp, fields) => {
return Object.keys(fields).reduce((prev, key) => {
const path = fields[key];
const field = {
get() {
const namespace = this[namespaceProp];
const getterPath = `${namespace}/getField`;
return this.$store.getters[getterPath](path);
},
set(value) {
const namespace = this[namespaceProp];
const mutationPath = `${namespace}/updateField`;
this.$store.commit(mutationPath, { path, value });
}
};
prev[key] = field;
return prev;
}, {});
};
export default {
name: "App",
data() {
return {
nsProp: "fooModule"
};
},
computed: {
...mapFields2("nsProp", { foo: "foo", bar: "bar" })
}
};
</script>
Store
import Vue from "vue";
import Vuex from "vuex";
import { getField, updateField } from "vuex-map-fields";
import App from "./App";
Vue.use(Vuex);
Vue.config.productionTip = false;
const store = new Vuex.Store({
modules: {
fooModule: {
namespaced: true,
state: {
foo: "initial foo value",
bar: "initail bar value"
},
getters: {
getField
},
mutations: {
updateField
}
}
}
});
new Vue({
el: "#app",
components: { App },
store,
template: "<App/>"
});

Vue dynamic mapGetters

I have a props that i want to use to make a dynamic mapGetters but the the mapGetters sees the props as undefined, probably because the computed is loaded before the props. Do anybody know how i can make it dynamic? my code is as follow:
export default {
props: ['listType'],
components: {
addrow: AddRow
},
computed: {
...mapGetters({
list: `${this.listType}/list`,
current: 'Dropdown/current'
})
},
}
[UPDATE]
I have found the solution thanks to #boussadjrabrahim
My working code look like this:
export default {
props: ['listType'],
components: {
addrow: AddRow
},
computed: {
...mapGetters({
current: 'Dropdown/current'
}),
...mapState({
list (state, getters) {
return getters[`${this.listType}/list`]
}
})
}
}
You can also use this.$store for complete access to the store. So, list would become
export default {
props: ['listType'],
computed: {
list() {
return this.$store.getters[`${this.listType}/list`]
}
}
}
Use the dispatch method to trigger an action, like so
export default {
props: ['listType'],
methods: {
sortList(order) {
this.$store.dispatch(`${this.listType}/list`, order)
}
}
}
What I found that worked was essentially rolling your own mapGetters method in the created() stage of the lifecycle.
Note that this solution has NOT been fully vetted and I have no idea what, if any "gotchas" it may create. As always, caveat emptor.
export default {
props: ['listType'],
components: {
addrow: AddRow
},
created() {
// This can be configured a number of ways, but for brevity:
const mappableGetters = {
list: `${this.listType}/list`,
current: 'Dropdown/current'
}
Object.entries(mappableGetters).forEach(([key, value]) => {
// Dynamically defines a new getter on `this`
Object.defineProperty(this, key, { get: () => { return this.$store.getters[value] } })
})
// Now you can use: `this.list` throughout the component
},
computed: {
// Roll your own mapper above in `created()`
// ...mapGetters({
// list: `${this.listType}/list`,
// current: 'Dropdown/current'
// })
}
}

Categories