Make Vuex data reactive - vue.js

I'm looking for a solution to make my vuex data reactive.
Let me explain the context. I render a list from vuex data with
`computed: {
...mapGetters(["groups"])
},`
This list can be modified by the user with a drag and drop system. (I use https://github.com/Vivify-Ideas/vue-draggable FYI)
The problem is that data is not reactive. Usually I let the data in the data() vuejs propertie, so when a user modify the list, the data is reactively updated.
So is it possible to import the data from vuex to data() properties ? So:
1/ data from vuex is "imported" in data()
2/ data() is modified by user interactions
3/ data() is saved when necessary in vuex (in my const state = {}).
I didn't found my hapiness in my last search =(

You can try using the watch VueJS property
<template>
// I never used this component, but I think (from the docs said) is used like this.
<div v-drag-and-drop:options="myData">
<ul>
<li>Item 1</li>
...
</ul>
...
</div>
</template>
<script>
...
data () {
return {
myData: null, // or whatever
}
},
watch: {
myData() {
// if myData changed update the store.
this.$store.commit('updateMyData', myData);
}
},
created() {
this.myData = this.$store.state.myStore.myStoreData;
}
...
</script>
If the myData is updated then update the store, and if the store change, update your components data.
Your store should be looking like this
export default {
mutations: {
updateMyData(state, value) {
state.myData = value;
},
},
state: {
myData: {} // or whatever
},
};
Basically you need to update Vuex store when your component data change.
If you need more infos feel free to comment this answer.
Hope this help you.

Related

Simple Vue store pattern - initial server fetch not reacting

This app isn't complicated. I'm trying to create a simple store (not keen to use Vuex for something this light) which should coordinate server requests and make sure there's a single source of truth across the app.
store.js
import Vue from "vue"
import axios from "axios"
class Store {
items = []
constructor() {
this.fetchData()
}
fetchData() {
axios
.get("/api/items")
.then(response => this.fillFieldsFromServer(response.data))
}
fillFieldsFromServer(data) {
// NONE OF THESE WORK
// 1: this.items = data
// 2: this.items = this.items.concat(data)
// 3: Array.prototype.push.apply(this.items, data)
}
}
export const itemStore = Vue.observable(new Store())
component.vue
<template>
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
</template>
<script>
import { itemStore } from "../../stores/item-store.js"
export default {
computed: {
items() {
return itemStore.items
},
},
}
</script>
Obviously I'm fundamentally misunderstanding something here.
What I thought would happen:
The store singleton is created
A server request is fired off
Vue makes the store singleton reactive
The component renders with an empty list
The component watches store.items
The server request returns
The store updates items
The component sees that changes
The component re-renders with the server data
But what's actually happening is that step (8) doesn't occur. The server request returns fine, but the component doesn't see the change so it doesn't re-render.
Obviously I'm doing something wrong. But what?
Vue.observable makes an object reactive by recursively replacing existing properties with get/set accessors, this allows to detect when they are changed. As for arrays, Array.prototype methods that mutate existing array are also replaced to track their calls.
This isn't supposed to work because Array.prototype.push.apply !== store.items.push:
Array.prototype.push.apply(this.items, data)
It should be either:
fillFieldsFromServer(data) {
this.items = data;
}
Or:
fillFieldsFromServer(data) {
this.items.push(...data);
}
Here is a demo.

How to get data from vuex state into local data for manipulation

I'm having trouble understanding how to interact with my local state from my vuex state. I have an array with multiple items inside of it that is stored in vuex state. I'm trying to get that data from my vuex state into my components local state. I have no problems fetching the data with a getter and computed property but I cannot get the same data from the computed property into local state to manipulate it. My end goal is to build pagination on this component.
I can get the data using a getters and computed properties. I feel like I should be using a lifecycle hook somewhere.
Retrieving Data
App.vue:
I'm attempting to pull the data before any components load. This seems to have no effect versus having a created lifecycle hook on the component itself.
export default {
name: "App",
components: {},
data() {
return {
//
};
},
mounted() {
this.$store.dispatch("retrieveSnippets");
}
};
State:
This is a module store/modules/snippets.js
const state = {
snippets: []
}
const mutations = {
SET_SNIPPETS(state, payload) {
state.snippets = payload;
},
}
const actions = {
retrieveSnippets(context) {
const userId = firebase.auth().currentUser.uid;
db.collection("projects")
.where("person", "==", userId)
.orderBy("title", "desc")
.onSnapshot(snap => {
let tempSnippets = [];
snap.forEach(doc => {
tempSnippets.push({
id: doc.id,
title: doc.data().title,
description: doc.data().description,
code: doc.data().code,
person: doc.data().person
});
});
context.commit("SET_SNIPPETS", tempSnippets);
});
}
}
const getters = {
getCurrentSnippet(state) {
return state.snippet;
},
Inside Component
data() {
return {
visibleSnippets: [],
}
}
computed: {
stateSnippets() {
return this.$store.getters.allSnippets;
}
}
HTML:
you can see that i'm looping through the array that is returned by stateSnippets in my html because the computed property is bound. If i remove this and try to loop through my local state, the computed property doesn't work anymore.
<v-flex xs4 md4 lg4>
<v-card v-for="snippet in stateSnippets" :key="snippet.id">
<v-card-title v-on:click="snippetDetail(snippet)">{{ snippet.title }}</v-card-title>
</v-card>
</v-flex>
My goal would be to get the array that is returned from stateSnippets into the local data property of visibleSnippets. This would allow me to build pagination and manipulate this potentially very long array into something shorter.
You can get the state into your template in many ways, and all will be reactive.
Directly In Template
<div>{{$store.state.myValue}}</div>
<div v-html='$store.state.myValue'></div>
Using computed
<div>{{myValue}}</div>
computed: {
myValue() { return this.$store.state.myValue }
}
Using the Vuex mapState helper
<div>{{myValue}}</div>
computed: {
...mapState(['myValue'])
}
You can also use getters instead of accessing the state directly.
The de-facto approach is to use mapGetters and mapState, and then access the Vuex data using the local component.
Using Composition API
<div>{{myValue}}</div>
setup() {
// You can also get state directly instead of relying on instance.
const currentInstance = getCurrentInstance()
const myValue = computed(()=>{
// Access state directly or use getter
return currentInstance.proxy.$store.state.myValue
})
// If not using Vue3 <script setup>
return {
myValue
}
}
I guess you are getting how Flux/Vuex works completely wrong. Flux and its implementation in Vuex is one way flow. So your component gets data from store via mapState or mapGetters. This is one way so then you dispatch actions form within the component that in the end commit. Commits are the only way of modifying the store state. After store state has changed, your component will immediately react to its changes with latest data in the state.
Note: if you only want the first 5 elements you just need to slice the data from the store. You can do it in 2 different ways:
1 - Create a getter.
getters: {
firstFiveSnipets: state => {
return state.snipets.slice(0, 5);
}
}
2 - Create a computed property from the mapState.
computed: {
...mapState(['allSnipets']),
firstFiveSnipets() {
return this.allSnipets.slice(0, 5);
}
}

vue.js two way data-binding between components

Please take a look at this not-working pseudo code:
Vue.component('child', {
props: [],
template: '<div><input v-model="text"></div>',
data: function() {
return {child-text: ""}
}
})
Vue.component('parent', {
template: '<h1> {{text}} </h1>'
data: function() {
return {parent-text: ""}
}
})
What is the most elegant way to fix this code that whenever the user changes the content of input box in child component, then the variable child-text in child component and the variable parent-text in parent component will change automatically? I also want that if the variable child-text and/or parent-text change then the content of input box will change respectively?
I solved this with my own little data store, its a very simple approach but works good enough for me without the necessity to dive into Vuex.
First, I create my data store somewhere before initializing anything else.
window.globalData = new Vue({
data: {
$store: {}
},
});
After that, I add a global Mixin that allows to get and set data to the global storage.
Vue.mixin({
computed: {
$store: {
get: function () { return window.globalData.$data.$store },
set: function (newData) { window.globalData.$data.$store = newData; }
}
}
});
Then, every component can access the data storage by this.$store. You can check a working example here:
https://codesandbox.io/s/62wvro7083

Parametized getter in Vuex - trigger udpate

My Vuex store has a collection of data, say 1,000 records. There is a getter with a parameter, getItem, taking an ID and returning the correct record.
I need components accessing that getter to know when the data is ready (when the asynchronous fetching of all the records is done).
However since it's a parametized getter, Vue isn't watching the state it depends on to know when to update it. What should I do?
I keep wanting to revert to a BehaviorSubject pattern I used in Angular a lot, but Vuex + rxJS seems heavy for this, right?
I feel I need to somehow emit a trigger for the getter to recalculate.
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import VueResource from 'vue-resource'
Vue.use(VueResource);
Vue.use(Vuex)
export default new Vuex.Store({
state: {
numberOfPosts : -1,
posts : {}, //dictionary keyed on slug
postsLoaded : false,
},
getters : {
postsLoaded : function(state){
return state.postsLoaded;
},
totalPosts : function(state){
return state.numberOfPosts;
},
post: function( state ){
return function(slug){
if( state.posts.hasOwnProperty( slug ) ){
return state.posts.slug;
}else{
return null;
}
}
}
},
mutations: {
storePosts : function(state, payload){
state.numberOfPosts = payload.length;
for( var post of payload ){
state.posts[ post.slug ] = post;
}
state.postsLoaded = true;
}
},
actions: {
fetchPosts(context) {
return new Promise((resolve) => {
Vue.http.get(' {url redacted} ').then((response) => {
context.commit('storePosts', response.body);
resolve();
});
});
}
}
})
Post.vue
<template>
<div class="post">
<h1>This is a post page for {{ $route.params.slug }}</h1>
<div v-if="!postsLoaded">LOADING</div>
<div v-if="postNotFound">No matching post was found.</div>
<div v-if="postsLoaded && !postNotFound" class="post-area">
{{ this.postData.title.rendered }}
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'post',
data : function(){
return {
loading : true,
postNotFound : false,
postData : null
}
},
mounted : function(){
this.postData = this.post( this.$route.params.slug );
if ( this.postData == null ){
this.postNotFound = true;
}
},
computed : mapGetters([
'postsLoaded',
'post'
])
}
</script>
As it stands, it shows the "post not found" message because when it accesses the getter, the data isn't ready yet. If a post isn't found, I need to distinguish between (a) the data is loaded and there isn't a post that matches, and (b) the data isn't loaded so wait
I suspect the problem lies with how your are setting the posts array in your storePosts mutation, specifially this line:
state.posts[ post.slug ] = post
VueJs can't track that operation so has no way of knowing that the array has updated, thus your getter is not updated.
Instead your need to use Vue set like this:
Vue.set(state.posts, post.slug, post)
For more info see Change Detection Caveats documentation
code sample of mark's answer
computed: {
...mapGetters([
'customerData',
])
},
methods: {
...mapActions(['customerGetRecords']),
},
created() {
this.customerGetRecords({
url: this.currentData
});
Sorry I can't use code to illustrate my idea as there isn't a running code snippet for now. I think what you need to do is that:
Access the vuex store using mapGetters in computed property, which you already did in Post.vue.
Watch the mapped getters property inside your component, in your case there would be a watcher function about postsLanded or post, whatever you care about its value changes. You may need deep or immediate property as well, check API.
Trigger mutations to the vuex store through actions, and thus would change the store's value which your getters will get.
After the watched property value changes, the corresponding watch function would be fired with old and new value and you can complete your logic there.

Proper way to dynamically set many inputs values, getting data from Vuex store

I'm pretty new to Vue. I have large form with many inputs. I want to have all data stored in Vuex store as properties of a class.
const store = new Vuex.Store({
state: {
cv: new CurriculumVitae()
I can easly update values like that:
<input #input="updateValue" name="name">
...
methods: {
updateValue(e) {
this.$store.commit('updateValue', {field: e.target.name, value: e.target.value});
}
},
....
const store = new Vuex.Store({
mutations: {
updateValue (state, payload) {
state.cv.data[payload.field] = payload.value;
}
}
How I could insert a CurriculumVitae class instance filled with data into store, and have all inputs prefilled with it's data automatically? I wouldn't want to have to write v-model for every input and fill it individually. Is there a nice, more dynamic way to achieve that?
Edit: I just want to have nice way to update objects. When creating new one - I don't need any input filled with anything. When editing - it would be nice to load old data into inputs. I like it to be dynamic though, not to write model for each input and return specific value from store. Sth like I do when saving updates into store: it's just one method for all inputs.
Does Vue provide a way to do that?
Export a function that will return data
// data.js
export function CurriculumVitae() {
return {data: {name: 'John Doe', gender: 'Male'} }
}
Import the function in a store file
// store.js
import {CurriculumVitae} from 'data'
Instantiate the function.
//store.js
const store = new Vuex.Store({
state: {
cvs: CurriculumVitae() // here
In a component file
// myComponent.vue
computed: {
cvs(){
return store.state.cvs // here
}
}
<input type="text" :value="cvs.data.name"> <!-- here -->
See if this works.