Vuex - Shared server side state? - vue.js

I am modifying hacker-news example and I added notifications component (If fetching external data goes wrong: save notification with error in vuex, after user reads it and clicks X remove it from vuex state). Looks like this:
[NOTIFY] (state, message) {
state.data.push(message)
},
[READ_NOTIFICATION] (state, index) {
state.data.splice(index, 1)
}
Problem: When data is being fetched and state is being set on server, it keeps error there in global state forever, which means that if I open new browser I will get the same old error from previous session.
Shouldn't server-side vuex state reset on every request? What am I missing?

I'm confused as to what you mean by "server-side state". Vuex is a client side store. What and why are you saving state to the server?
Furthermore, I cannot tell what you are doing with the code? Is that in a store.js file? In the mutations object?
You would need to wrap that in a vue method like so:
In the Vue file:
methods: {
clicksButton (idx) {
this.$store.commit('read_notification', idx)
}
}
In the store.js file:
const store = new Vuex.Store({
state: {
data: [],
error: null
},
mutations: {
read_notification (state, idx) {
state.data.splice(idx, 1)
}
}
}

Related

Issue using Socket.io with Pinia

I'm trying to use Pinia to manage some global state in a Vue.js app I'm building, specifically I want to share a Socket.io instance between various components and views. However I'm getting a
this.socketObject.emit is not a function
error when calling functions from the Socket.io instance, when I call them from a component other than the component/view the Socket.io instance was created in. Here's some excerpts of the code.
#/views/LobbyView.vue (This is where I create the Socket.io instance and pass it to the Pinia store, I can use emit fine in this file without any errors)
import io from "socket.io-client";
import { useSocket} from "#/store/index";
...
setup() {
const socketObject = useSocket();
return { socketObject};
},
...
async mounted() {
this.socketObject = await io("http://localhost:8000");
this.socketObject.emit("createNewRoom");
}
#/store/index.js Pinia store
import { defineStore } from "pinia";
...
export const useSocket = defineStore({
id: "socket",
state: () => {
return {socketObject: Object};
},
getters: {},
actions: {},
});
#/components/lobbySettings (this is the file where I have issues using Socket.io in via my Pinia store)
import { useSocket } from "#/store/index";
...
setup() {
const socketObject = useSocket();
return { socketObject};
},
...
methods: {
startGame() {
this.socketObject.emit("updateRoom", this.roomInfo);
},
},
When the start game method is called on a button press, if I catch the error I get
this.socketObject.emit is not a function
I don't quite understand why Pinia isn't giving me access to functions from my Socket.io instance, the store seems to be working fine for other data in my app, just cant call these functions.
useSocket returns a store, not socket instance. It should be used as:
const socketStore = useSocket();
...
socketStore.socketObject.emit(...)
io(...) doesn't return a promise, it's semantically incorrect to use it with await.
The use of Object constructor is incorrect. If a value is uninitialized, it can be null:
state: () => {
return {socketObject: null};
},
The mutation of store state outside the store is a bad practice. All state modifications should be performed by actions, this way they can be easily tracked through devtools, this is one of benefits of using a store.
At this point there's no benefit from packing socketObject inside a store. Socket instance could be either used separately from a store, or socket instance could be abstracted away and made reactive with store actions, etc.

Vue, looking for a way to watch values/changes from a different component/js file

So I have a plain js file which contains some stuff like functions, one of these is a function that gets a value from 1. localstorage if not present 2. vuex if not present use a default value. These values can be updated thru the whole app in several components which means that components that also are using this value neer te update this value. For now I cant seem to make this part reactive.
helper.js
export const helperFunc = () => {
let value
//dummy
if(checkforlocalstorage){
value = localstorage_value
} else {
value = other_value
}
return value
}
ComponentOne.vue
<template>
<div>{{dynamicValue}}</div>
</template>
<script>
import {helperFunc} from './plugins/helpers.js'
export default {
data () {
return {
}
},
computed: {
dynamicValue : function () {
return helperFunc()
}
},
}
<script>
ComponentTwo.vue
ComponentThree.vue
Update the values here
local or session storage is not a reactive data and vue can't watch them. so you better set the variable in a vuex store, this way the data is reactive and all the components can access it from everywhere in the app.
but there's a catch that you might run into and that is if you refresh the page, all the data in the store get lost and you get the initial values.
but there is one thing you can do, and that is:
save the variable in the localStorage to have it even after the page refresh
initialize the store state value to have the data reactive and accessible through out the app
update the state and localStorage every-time data changes so you can have the reactivity and also latest value in case of a page reload.
Here I show you the basic of this idea:
first you need to setup the store file with the proper state, mutation and action:
export default {
state() {
return {
myVar: 'test',
}
},
mutations: {
UPDATE_MY_VAR(state, value) {
state.myVar = value;
}
},
actions: {
updateMyVar({ commit }, value) {
localStorage.setItem('myVar', value);
commit('UPDATE_MY_VAR', value);
},
initializeMyVar({ commit }) {
const value = localStorage.getItem('myVar');
commit('UPDATE_MY_VAR', value);
}
}
}
then in the beforeCreate or created hook of the root component of your app you'll have:
created() {
this.$store.dispatch('initializeMyVar');
}
this action read the data from a localStorage and initialize the myVar state and you can access that form everywhere like $store.state.myVar and this is reactive and can be watched. also if there is no localStorage and you need a fallback you can write the proper logic for this.
then whenever the data needs to be changed you can use the second action like $store.dispatch('updateMyVar', newUpdatedValue) which updates both the localStorage and the state.
now even with a page reload you get the latest value from the localStorage and the process repeats.

Updating getter value Vuex store when state changes

I'm trying to figure out how to properly update a getter value when some other variable from VueX changes/updates.
Currently I'm using this way in a component to update:
watch: {
dates () {
this.$set(this.linedata[0].chartOptions.xAxis,"categories",this.dates)
}
}
So my getter linedata should be updated with dates value whenever dates changes. dates is state variable from VueX store.
The thing is with this method the value won't be properly updated when I changed route/go to different components. So I think it's better to do this kind of thing using the VueX store.
dates is updated with an API call, so I use an action to update it.
So the question is how can I do such an update from the VueX store?
EDIT:
I tried moving this to VueX:
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit("SET_DATA", {
this.linedata[0].chartOptions.xAxis,"categories": response.data.dates1,
this.linedata[1].chartOptions.xAxis,"categories": response.data.dates2
});
}
SET_DATA(state, payload) {
state = Object.assign(state, payload);
}
But the above does not work, as I cannot set nested object in action this way...
Getters are generally for getting, not setting. They are like computed for Vuex, which return calculated data. They update automatically when reactive contents change. So it's probably best to rethink the design so that only state needs to be updated. Either way, Vuex should be updated only with actions/mutations
Given your example and the info from all your comments, using linedata as state, your action and mutation would look something like this:
actions: {
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit('SET_DATA', response.data.dates);
}
}
mutations: {
SET_DATA(state, dates) {
Vue.set(state.linedata[0].chartOptions.xAxis, 'categories', dates[0]);
Vue.set(state.linedata[1].chartOptions.xAxis, 'categories', dates[1]);
}
}
Which you could call, in the component for example, like:
this.$store.dispatch('loadData');
Using Vue.set is necessary for change detection in this case and requires the following import:
import Vue from 'vue';
Theoretically, there should be a better way to design your backend API so that you can just set state.linedata = payload in the mutation, but this will work with what you have.
Here is a simple example of a Vuex store for an user.
export const state = () => ({
user: {}
})
export const mutations = {
set(state, user) {
state.user = user
},
unset(state) {
state.user = {}
},
patch(state, user) {
state.user = Object.assign({}, state.user, user)
}
}
export const actions = {
async set({ commit }) {
// TODO: Get user...
commit('set', user)
},
unset({ commit }) {
commit('unset')
},
patch({ commit }, user) {
commit('patch', user)
}
}
export const getters = {
get(state) {
return state.user
}
}
If you want to set the user data, you can call await this.$store.dispatch('user/set') in any Vue instance. For patching the data you could call this.$store.dispatch('user/patch', newUserData).
The getter is then reactively updated in any Vue instance where it is mapped. You should use the function mapGetters from Vuex in the computed properties. Here is an example.
...
computed: {
...mapGetters({
user: 'user/get'
})
}
...
The three dots ... before the function call is destructuring assignment, which will map all the properties that will the function return in an object to computed properties. Those will then be reactively updated whenever you call dispatch on the user store.
Take a look at Vuex documentation for a more in depth explanation.

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);
}
}

Mutation not committing state vuex - Nuxt

I am working on vuex app, on my other page i am committing changes to state its working, but here on this specific page i am getting some data from api and storing it in store but it get stuck in mutation, I am getting all data in mutation payload but its not effecting the changes, Please check the screenshot and code,
I can not create fiddle cuz it works there
Getting Items
async getItems () {
await this.$axios.get(`/api/projects/w/latest/all`)
.then(response => {
this.$store.commit('project/UPDATE_PROJECTS', response.data.items)
});
}
Action
updateProjectsAction (context, projects) {
context.commit('UPDATE_PROJECTS', projects)
},
Mutation
UPDATE_PROJECTS (state, payload ) {
state.projects = payload
}
State
projects: {},
Response
When i click load state or manually commit these changes it gives me this error.
In getting items
this.$store.commit('project/UPDATE_PROJECTS', response.data.items)
you should change it to
this.$store.dispatch('project/updateProjectsAction', response.data.items)
In Action you should change
updateProjectsAction ({commit}, projects) {
commit('UPDATE_PROJECTS', projects)},
And last thing, you should have getters for getting data from vuex
getters:{
getProjects: state =>{
return state.project
}
}
In .vue
import 'mapGetters' from 'vuex'
export default {
computed:{
...mapGetters({
projects: project/getProjects
})
}
}
getters and computed will helps your vue app reactive