Vuex complains that a new instance of the store cannot be created without calling Vue.use(Vuex). While this is okay generally, I am fiddling with the idea of writing a backend/frontend using the same store. Anybody know the answer?
Thanks.
TL;DR you can perfectly use Vuex in node (without a browser), even for unit testing. Internally, though, Vuex still uses some code from Vue.
You can't use Vuex without Vue. Because:
Vuex checks for the existence of Vue.
Vuex depends largely on Vue for its reactivity inner workings.
That being said, you do require Vue, but you don't require a Vue instance. You don't even require the browser.
So yes, it is pretty usable in the server-side, standalone.
For instance, you could run it using Node.js as follows:
Create a sample project:
npm init -y
Install the dependencies (note: axios is not necessary, we are adding it just for this demo):
npm install --save vue vuex axios
Create a script (index.js):
const axios = require('axios');
const Vue = require('vue');
const Vuex = require('vuex');
Vue.use(Vuex);
const store = new Vuex.Store({
strict: true,
state: {name: "John"},
mutations: {
changeName(state, data) {
state.name = data
}
},
actions: {
fetchRandomName({ commit }) {
let randomId = Math.floor(Math.random() * 12) + 1 ;
return axios.get("https://reqres.in/api/users/" + randomId).then(response => {
commit('changeName', response.data.data.first_name)
})
}
},
getters: {
getName: state => state.name,
getTransformedName: (state) => (upperOrLower) => {
return upperOrLower ? state.name.toUpperCase() : state.name.toLowerCase()
}
}
});
console.log('via regular getter:', store.getters.getName);
console.log('via method-style getter:', store.getters.getTransformedName(true));
store.commit('changeName', 'Charles');
console.log('after commit:', store.getters.getName);
store.dispatch('fetchRandomName').then(() => {
console.log('after fetch:', store.getters.getName);
});
Run it:
node index.js
It will output:
via regular getter: John
via method-style getter: JOHN
after commit: Charles
after fetch: Byron
Related
I have some queries from an API-Server that returns a json object that will be static over a user session, but not static forever.
It's a one-pager with Vue router.
How can I achieve that I:
can access this.myGlobals (or similar eg window.myGlobals) in all components, where my prefetched json-data from API-Server is stored.
My approach that is already working is to embed help.js via a mixin.
Oddly enough, I get hundreds of calls to this query. At first I thought that it only happened in the frontend and is chached, but the requests are actually sent hundreds of times to the server. I think it is a mistake of my thinking, or a systematic mistake.
i think the problem is, that the helper.js is not static living on the vue instance
main.js:
import helpers from './helpers'
Vue.mixin(helpers)
helpers.js:
export default {
data: function () {
return {
globals: {},
}
}, methods: {
//some global helper funktions
},
}, mounted() {
let url1 = window.datahost + "/myDataToStore"
this.$http.get(url1).then(response => {
console.log("call")
this.globals.myData = response.data
});
}
}
log in console:
call
SomeOtherStuff
(31) call
SomeOtherStuff
(2) call
....
log on server:
call
call
call (pew pew)
My next idea would be to learn vuex, but since its a easy problem, im not sure if i really need that bomb ?
You can use plugin to achieve this.
// my-plugin.js
export default {
install (Vue, options) {
// start fetching data right after install
let url1 = window.datahost + "/myDataToStore"
let myData
Vue.$http.get(url1).then(response => {
console.log("call")
myData = response.data
})
// inject via global mixin
Vue.mixin({
computed: {
myData () {
return myData
}
}
})
// or inject via instance property
Vue.prototype.$myData = myData
// or if you want to wait until myData is available
Vue.prototype.$myData = Vue.$http.get(url1)
.then(response => {
console.log("call")
myData = response.data
})
}
}
and use it:
Vue.use(VueResource)
Vue.use(myPlugin)
I am using Gatsby as a Static Site Generator and using Netlify to deploy.
Netlify lets you set Environment Variables in its UI backend.
I've set a few env vars in the Netlify backend to be able to post subscribers to a mailing list.
DATA_NO = 'XXXX'
LIST_ID = '123456'
API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXX'
In my src files, I've got a component that responds to a onSubmit event and constructs a URL to post a new subscriber.
(axios is used as a package for sending HTTP requests, etc)
import React, { useState } from "react"
import axios from 'axios'
const Form = () => {
const [userEmail, setState] = useState({'email_address': ''})
const creds = 'anystring:'+ process.env.API_KEY
let URL = 'https://'+ process.env.DATA_NO +'.api.example.com/3.0'
URL += '/lists/'+ process.env.LIST_ID +'/members'
const submitSubscribe = async e => {
e.preventDefault()
const payload = {
'email_address': userEmail.email_address,
'status': 'subscribed'
}
try {
const response = await axios.post( URL , payload, {
headers: {
'Authorization': 'Basic ' + Buffer.from(creds ).toString('base64')
}
})
console.log('r', response)
console.log('r data', response.data)
} catch(err) {
console.log(err);
}
}
return (
<form name="newsletter-signup" method="post" onSubmit={submitSubscribe}>
{/*<input type="hidden" name="form-name" value="newsletter-signup" />*/}
<input type="email" placeholder="Email required" onChange={handleChange} value={userEmail.email_address} required />
<button type="submit" className="button primary-button-inverted">Send'</button>
</form>
)
}
So, what's happening is that on RUN time, my env vars are coming out as undefined.
I've been on the Netlify docs and they keep saying you need to interpolate the values to the client to be able to use them. I understand the logic here. These env vars need to be printed and bundled during build time, not invoked at run time.
The question I'm struggling with is HOW do I do this?
I have set up a .env.development file in the root of my project. I have tried prefixing my env vars with GATSBY_ but I still have the same trouble.
I tried using require('dotenv').config() but I'm not sure where exactly to put that (in my gatsby-node.js, gatsby-config.js) or do I need to include on the page with my component that is using these env vars.
I'd like to be able to set these vars up in one place (maybe two if testing in development) but I don't want to much tweaking involved to be able to use them in both development and production builds.
I also understand that Netlify or Gatsby can process these vars into a functions/ folder in my source code that I can somehow make use of but that seems like more than I need to just post a simple form.
Please help!
Update
Current code:
In my project root, I created two .env files, one for development and one for production. They each share the following format (remember, I am developing in GatsbyJS):
GATSBY_MC_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxx-xxxx"
GATSBY_MC_DATA_NO="xxxx"
GATSBY_MC_AUDIENCE_ID="xxxxxxxxxxx"
I've set up a separate config.js file in src/config/config.js to organize and validate my env vars (thanks #Baboo_). It looks like:
export const MC_API_KEY = process.env.GATSBY_MC_API_KEY;
export const MC_DATA_NO = process.env.GATSBY_MC_DATA_NO;
export const MC_AUDIENCE_ID = process.env.GATSBY_MC_AUDIENCE_ID;
const envVars = [
{name: "MC_API_KEY", value: MC_API_KEY},
{name: "MC_DATA_NO", value: MC_DATA_NO},
{name: "MC_AUDIENCE_ID", value: MC_AUDIENCE_ID}
]
export const checkEnvVars = () => {
const envVarsNotLoaded = envVars.filter((envVar) => envVar.value !== undefined);
if (envVarsNotLoaded.length > 0) {
throw new Error(`Could not load env vars ${envVarsNotLoaded.join(",")}`);
}
}
checkEnvVars()
However, when I run gatsby develop, the "Could not load env vars" error gets thrown.
You are doing it the right way.
What you have to do is indeed prefix your environment variables with GATSBY_, Gatsby will automatically load them. Then call them in your code:
const creds = 'anystring:'+ process.env.GATSBY_API_KEY
let URL = 'https://'+ process.env.GATSBY_DATA_NO +'.api.example.com/3.0'
tURL += '/lists/'+ process.env.GATSBY_LIST_ID +'/members'
Make sure to use the whole string process.env.GATSBY_LIST_ID instead of process.env[GATSBY_LIST_ID] because the object process.env is undefined.
Locally
Make sure to create to .env files, .env.development and .env.production. The former is used when you run gatsby develop and the latter when you run gatsby build.
You may already know that you shouldn't commit these files.
Netlify
Add the same environment variables in your deployment pipeline on Netlify. Here is the related doc. This way Netlify can build your webiste when being deployed.
Improvements
Instead of refering environment variables directly, create a file where they are loaded and if one of them cannot be retrieved, throw an error. This way you will be noticed when the loading fails and save debugging time.
Example:
// config.js
export const API_KEY = process.env.GATSBY_API_KEY;
export const DATA_NO = process.env.GATSBY_DATA_NO ;
const envVars = [
{name: "API_KEY", value: API_KEY},
{name: "DATA_NO", value: DATA_NO},
]
const checkEnvVars = () => {
const envVarsNotLoaded = envVars.filter(isUndefined);
if (envVarsNotLoaded.length > 0) {
throw new Error(`Could not load env vars ${envVarsNotLoaded.join(",")}`);
}
}
const isUndefined = (envVar) => typeof envVar.value === "undefined";
// component.js
import React, { useState } from "react"
import axios from 'axios'
// Import environment variables
import { API_KEY, DATA_NO } from "./config"
const Form = () => {
// ...
const [userEmail, setState] = useState({'email_address': ''})
const creds = 'anystring:'+ API_KEY
let URL = 'https://'+ DATA_NO +'.api.example.com/3.0'
You need to add a different env file for the two environments to make this work.
Meaning .env.development and .env.production.
I'm facing a weird issue with my Electron + Vue setup.
CONDITIONS: Electron + Vue (I used a boilerplate) + vuex-persist (also tried vuex-persistedstate and vuex-persistfile).
PROBLEM: Vuex getters remain 0/null/'' when the store is being rehydrated. How do I know that? If the local storage is clean (I launch the app for the first time), first mutations update state (getters return correct values) and I can see an object being added to the browser's local storage. When the app is restarted, however, mutations trigger state and local storage update just like before, BUT getters remain empty/default. Below getter returns an empty array.
SETUP: I have an app that works with 3rd party API: gets data, calculates stuff and sends some data back. API also requires authorization. Below is my Vuex structure.
Part of my state object...
const state = {
token: '',
projects: [],
work_packages: [],
timeEntriesLocal: []
}
...and one of my getters:
const getters = {
todayLog () {
function sameDay (d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate()
}
var entries = state.timeEntriesLocal
var todayEntries = []
entries.forEach(element => {
var date = element.spentOn
var today = new Date()
if (sameDay(date, today)) {
todayEntries.push(element)
}
})
return todayEntries
}
}
It returns entries from an array that are due to "today".
timeEntriesLocal is filled with this method:
addTimeEntry () {
let entry = {
id: this.$store.state.UserData.timeEntriesLocal.length + 1,
project: this.getItemById(this.$store.state.UserData.current.project_id, this.$store.state.UserData.projects),
spentOn: new Date(),
comment: this.comment,
activityId: this.activityId,
workPackage: this.getItemById(this.$store.state.UserData.current.work_package_id, this.$store.state.UserData.work_packages),
hours: this.precisionRound(this.$store.state.UserData.duration / 60 / 60, 2),
published: false,
loading: false,
infoMessage: ''
}
this.$store.commit('ADD_LOCAL_ENTRY', entry)
}
And lastly here's the mutation that I have just used above:
ADD_LOCAL_ENTRY (state, entry) {
state.timeEntriesLocal.unshift(entry)
}
The changes in timeEntriesLocal are not being picked up, since you're changing the length of the Array. This is a limitation of JavaScript covered in Common Gotchas section of the docs. A way to go around it is also covered in Vuex`s docs:
Mutations Follow Vue's Reactivity Rules
Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components
observing the state will update automatically. This also means Vuex
mutations are subject to the same reactivity caveats when working with
plain Vue:
Prefer initializing your store's initial state with all desired fields
upfront.
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one. For example, using the object
spread syntax we can write it like this: state.obj = { ...state.obj, newProp: 123 }
So in your exmample, to make the Vue (Vuex) detect these changes, you can do something like:
ADD_LOCAL_ENTRY (state, entry) {
state.timeEntriesLocal.unshift(entry);
Vue.set(state, 'timeEntriesLocal', state.timeEntriesLocal);
}
I have setup hot reloading and dynamic loading of my vuex modules.
store.js file - hot update section
if (module.hot) {
// accept actions and mutations as hot modulesLoader
module.hot.accept([
'./store/modules/index.js',
'./store/helpers/global-actions',
'./store/helpers/global-mutations',
...modulePaths,
// './store/helpers/global-actions',
], () => {
let newModules = require('./store/modules').modules
store.hotUpdate({
actions: require('./store/helpers/global-actions'),
mutations: require('./store/helpers/global-mutations'),
modules: newModules,
})
})
}
modules/index.js file
const requireModule = require.context('.', true, /index.js$/)
const modules = {}
const modulePaths = []
requireModule.keys().forEach(fileName => {
if (fileName === './index.js') {
modulePaths.push(fileName.replace('./', './store/modules/'))
return
} else {
let moduleName = fileName.match(/(?<=\/)\w*(?=\/)/g)[0]
modulePaths.push(fileName.replace('./', './store/modules/'))
modules[moduleName] =
{
namespaced: false,
...requireModule(fileName),
}
}
})
export {modulePaths, modules}
Basically what this code does is loading folders with index.js file as modules (where module name is foldername) dynamically.
If I update module actions or getters or mutations everything works as expected I do get new actions added to store as well as mutations, when either of modules is updated.
The only thing I can't get to work is to get modules state changed on update. So if I change modules state it does not get reflected. Is it a normal behaviour? Or am I doing something wrong?
I'm attempting to watch for localstorage:
Template:
<p>token - {{token}}</p>
Script:
computed: {
token() {
return localStorage.getItem('token');
}
}
But it doesn't change, when token changes. Only after refreshing the page.
Is there a way to solve this without using Vuex or state management?
localStorage is not reactive but I needed to "watch" it because my app uses localstorage and didn't want to re-write everything so here's what I did using CustomEvent.
I would dispatch a CustomEvent whenever you add something to storage
localStorage.setItem('foo-key', 'data to store')
window.dispatchEvent(new CustomEvent('foo-key-localstorage-changed', {
detail: {
storage: localStorage.getItem('foo-key')
}
}));
Then where ever you need to watch it do:
mounted() {
window.addEventListener('foo-key-localstorage-changed', (event) => {
this.data = event.detail.storage;
});
},
data() {
return {
data: null,
}
}
Sure thing! The best practice in my opinion is to use the getter / setter syntax to wrap the localstorage in.
Here is a working example:
HTML:
<div id="app">
{{token}}
<button #click="token++"> + </button>
</div>
JS:
new Vue({
el: '#app',
data: function() {
return {
get token() {
return localStorage.getItem('token') || 0;
},
set token(value) {
localStorage.setItem('token', value);
}
};
}
});
And a JSFiddle.
The VueJs site has a page about this.
https://v2.vuejs.org/v2/cookbook/client-side-storage.html
They provide an example.
Given this html template
<template>
<div id="app">
My name is <input v-model="name">
</div>
<template>
They provide this use of the lifecycle mounted method and a watcher.
const app = new Vue({
el: '#app',
data: {
name: ''
},
mounted() {
if (localStorage.name) {
this.name = localStorage.name;
}
},
watch: {
name(newName) {
localStorage.name = newName;
}
}
});
The mounted method assures you the name is set from local storage if it already exists, and the watcher allows your component to react whenever the name in local storage is modified. This works fine for when data in local storage is added or changed, but Vue will not react if someone wipes their local storage manually.
Update: vue-persistent-state is no longer maintained. Fork or look else where if it doesn't fit your bill as is.
If you want to avoid boilerplate (getter/setter-syntax), use vue-persistent-state to get reactive persistent state.
For example:
import persistentState from 'vue-persistent-state';
const initialState = {
token: '' // will get value from localStorage if found there
};
Vue.use(persistentState, initialState);
new Vue({
template: '<p>token - {{token}}</p>'
})
Now token is available as data in all components and Vue instances. Any changes to this.token will be stored in localStorage, and you can use this.token as you would in a vanilla Vue app.
The plugin is basically watcher and localStorage.set. You can read the code here. It
adds a mixin to make initialState available in all Vue instances, and
watches for changes and stores them.
Disclaimer: I'm the author of vue-persistent-state.
you can do it in two ways,
by using vue-ls and then adding the listener on storage keys, with
Vue.ls.on('token', callback)
or
this.$ls.on('token', callback)
by using storage event listener of DOM:
document.addEventListener('storage', storageListenerMethod);
LocalStorage or sessionStorage are not reactive. Thus you can't put a watcher on them. A solution would be to store value from a store state if you are using Vuex for example.
Ex:
SET_VALUE:(state,payload)=> {
state.value = payload
localStorage.setItem('name',state.value)
or
sessionStorage.setItem('name',state.value)
}