Vue dynamic mapGetters - javascript

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'
// })
}
}

Related

Vue.js Typescript I get the data using getter but can't reach it in methods

I am new to Typescript with vuex. I simply want to fetch user list from the backend. Put in the store. I declared custom user type
export interface User {
id: number;
firstName: string;
lastName: string;
email: string;
}
in my vuex.d.ts file, I declare store module like:
import { Store } from "vuex";
import { User } from "./customTypes/user";
declare module "#vue/runtime-core" {
interface State {
loading: boolean;
users: Array<User>;
}
interface ComponentCustomProperties {
$store: Store<State>;
}
}
in my store I fetch the users successfully and commit the state:
import { createStore } from "vuex";
import axios from "axios";
import { User, Response } from "./customTypes/user";
export default createStore({
state: {
users: [] as User[], // Type Assertion
loading: false,
},
mutations: {
SET_LOADING(state, status) {
state.loading = status;
},
SET_USERS(state, users) {
state.users = users;
},
},
actions: {
async fetchUsers({ commit }) {
commit("SET_LOADING", true);
const users: Response = await axios.get(
"http://localhost:8000/api/get-friends"
);
commit("SET_LOADING", false);
commit("SET_USERS", users.data);
},
},
getters: {
userList: (state) => {
return state.users;
},
loadingStatus: (state) => {
return state.loading;
},
},
});
I set the getters, I sense that I don't need to set getter for just returning state however this is the only way I could reach the data in my component. Please advise if there is a better way to do it. In my component I accessed the data like:
<div class="friends">
<h1 class="header">Friends</h1>
<loading v-if="loadingStatus" />
<div v-else>
<user-card v-for="user in userList" :user="user" :key="user.id" />
<pagination />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { mapGetters } from "vuex";
import { User } from "../store/customTypes/user";
=import UserCard from "../components/UserCard.vue";
import Loading from "../components/Loading.vue";
import Pagination from "../components/Pagination.vue";
export default defineComponent({
name: "Friends",
components: {
UserCard,
Loading,
Pagination,
},
static: {
visibleUsersPerPageCount: 10,
},
data() {
return {
users: [] as User[],
currentPage: 1,
pageCount: 0,
};
},
computed: {
...mapGetters(["loadingStatus", "userList"]),
},
mounted() {
this.$store.dispatch("fetchUsers");
this.paginate()
},
methods: {
paginate () {
// this.users = this.$store.state.users
console.log(this.$store.state.users)
console.log(this.userList)
}
}
});
</script>
Now when I get userList with getters, I successfully get the data and display in the template. However When I want to use it in the method, I can't access it when component is mounted. I need to paginate it in the methods. So I guess I need to wait until promise is resolved however I couldn't figure out how. I tried
this.$store.dispatch("fetchUsers").then((res) => console.log(res)) didn't work.
What I am doing wrong here?
An action is supposed to return a promise of undefined, it's incorrectly to use it like this.$store.dispatch("fetchUsers").then(res => ...).
The store needs to be accessed after dispatching an action:
this.$store.dispatch("fetchUsers").then(() => {
this.paginate();
});

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

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:)

Vue 3.0 How to assign a prop to a ref without changing the prop

I'm sending from the parent component a prop: user. Now in the child component I want to make a copy of it without it changing the prop's value.
I tried doing it like this:
export default defineComponent({
props: {
apiUser: {
required: true,
type: Object
}
},
setup(props) {
const user = ref(props.apiUser);
return { user };
}
});
But then if I change a value of the user object it also changes the apiUser prop. I thought maybe using Object.assign would work but then the ref isn't reactive anymore.
In Vue 2.0 I would do it like this:
export default {
props: {
apiUser: {
required: true,
type: Object
}
},
data() {
return {
user: {}
}
},
mounted() {
this.user = this.apiUser;
// Now I can use this.user without changing this.apiUser's value.
}
};
Credits to #butttons for the comment that lead to the answer.
const user = reactive({ ...props.apiUser });
props: {
apiUser: {
required: true,
type: Object
}
},
setup(props) {
const userCopy = toRef(props, 'apiUser')
}
With the composition API we have the toRef API that allows you to create a copy from any source reactive object. Since the props object is a reactive, you use toRef() and it won't mutate your prop.
This is what you looking for: https://vuejs.org/guide/components/props.html#one-way-data-flow
Create data where you add the prop to
export default {
props: ['apiUser'],
data() {
return {
// user only uses this.apiUser as the initial value;
// it is disconnected from future prop updates.
user: this.apiUser
}
}
}
Or if you use api composition:
import {ref} from "vue";
const props = defineProps(['apiUser']);
const user = ref(props.apiUser);
You also may want to consider using computed methods (see also linked doc section from above) or v-model.
Please note that the marked solution https://stackoverflow.com/a/67820271/2311074 is not working. If you try to update user you will see a readonly error on the console. If you don't need to modify user, you may just use the prop in the first place.
As discussed in comment section, a Vue 2 method that I'm personally fond of in these cases is the following, it will basically make a roundtrip when updating a model.
Parent (apiUser) ->
Child (clone apiUser to user, make changes, emit) ->
Parent (Set changes reactively) ->
Child (Automatically receives changes, and creates new clone)
Parent
<template>
<div class="parent-root"
<child :apiUser="apiUser" #setUserData="setUserData" />
</div>
</template>
// ----------------------------------------------------
// (Obviously imports of child component etc.)
export default {
data() {
apiUser: {
id: 'e134',
age: 27
}
},
methods: {
setUserData(payload) {
this.$set(this.apiUser, 'age', payload);
}
}
}
Child
<template>
<div class="child-root"
{{ apiUser }}
</div>
</template>
// ----------------------------------------------------
// (Obviously imports of components etc.)
export default {
props: {
apiUser: {
required: true,
type: Object
}
},
data() {
user: null
},
watch: {
apiUser: {
deep: true,
handler() {
// Whatever clone method you want to use
this.user = cloneDeep(this.apiUser);
}
}
},
mounted() {
// Whatever clone method you want to use
this.user = cloneDeep(this.apiUser);
},
methods: {
// Whatever function catching the changes you want to do
setUserData(payload) {
this.$emit('setUserData', this.user);
}
}
}
Apologies for any miss types

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.

Vue component using computed property of the mixin

I have a simple component that uses mixin that's shared across multiple components with similar functionality.
When I run it I seem to be getting
Property or method "activeClass" is not defined on the instance but
referenced during render.
Here's my mixin
<script>
export default {
data() {
return {
opened: false,
identity: ''
}
},
computed: {
activeClass() {
return {
active: this.opened
};
}
},
created() {
window.EventHandler.listen(this.identity + '-toggled', opened => this.opened = opened);
},
methods: {
toggle() {
window.EventHandler.fire('toggle-' + this.identity);
}
}
}
</script>
and my component
<template>
<span class="pointer" :class="activeClass" #click="toggle"><i class="fas fa-search"></i></span>
</template>
<script>
import Trigger from '../../mixins/Trigger';
export default {
data() {
return {
mixins: [Trigger],
data() {
return {
identity: 'language'
}
}
}
}
}
</script>
For some reason I cannot seem to be able to access activeClass computed property from within the component. Any idea why is this happening?
Try to move mixin to components main scope. Not in data function rerurn

Categories