Using a Map inside of Vuex state - dictionary

I'm building a prototype module to learn about Vuex and running into what seems like a simple issue -- I'm having trouble using a Map as one of my state variables.
I want to use Vuex to store some basic user preferences across multiple modules and figured a Map would be a simple way to do it since the prefs could be handled as simple key/value pairs. But I'm either not defining the Map correctly, or I'm not using it correctly within the mutations.
<script lang="ts">
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
userSettings: Map,
},
mutations: {
addUserSetting(state, payload) {
if (state.userSettings == null) {
state.userSettings = new Map();
}
state.userSettings.set(payload.key, payload.value);
}
},
})
</script>
I'd be willing to use another object, and I started with an array of UserPref objects but that didn't go well, either.

Vue 2 doesn't have proper support for Map.
Assuming your keys are strings I suggest using an Object instead. To get a totally empty object (with no inherited properties, not even toString) you can use Object.create(null), or just use a normal object using {} if that seems unnecessary.
e.g.
import Vue from 'vue'
export default new Vuex.Store({
state: {
userSettings: Object.create(null),
},
mutations: {
addUserSetting(state, payload) {
Vue.set(state.userSettings, payload.key, payload.value);
}
}
})
Vue.set must be used as the properties are being added.

Related

How to specify region for functions using Angular Fire Module? [duplicate]

How to globally set default regions for firebase cloud functions via angular fire so that they are deployed to specified region? Or it doesn't have to be with angular fire. I just don't want to call .region('') method with every function declaration.
I can't find anything in documentation about how to do it. But I've heard that it should be possible.
Previous versions of angular fire should support it.
I was blind. It is mentioned in angular fire documentation, under appropriate chapter name Functions Region
in app.module.ts
import { AngularFireFunctionsModule, REGION } from '#angular/fire/functions';
...
...
providers: [
{ provide: REGION, useValue: 'asia-northeast1' }
]
But that's not the right answer. I need them deployed to the correct region, and it didn't occurre to me that angular fire can't do that.
So if you don't want to set region at every function definition, you have to prefix(?) the definition with a global utility, so you do it only in one place.
in functions-utils.ts
import { region } from 'firebase-functions';
export class FunctionsUtils {
public static get builder() {
return region('desired-region-from-possible-options');
}
}
and then use it in function declaration:
in index.ts or whatever
import { FunctionsUtils } from './functions-utils';
export const yourFunction = FunctionsUtils.builder.https.onCall(...
List of available regions

Sinon restore cannot read property 'restore' of undefined

I have the following code which I am using Sinon for
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { IncrementalSearch } from './IncrementalSearch';
describe('<IncrementalSearch />', () => {
afterEach(() => {
this.constructorSpy.restore();
});
it('calls constructor', () => {
this.constructorSpy = sinon.spy(IncrementalSearch, 'constructor');
const wrapper = mount(<IncrementalSearch />);
expect(IncrementalSearch.prototype.constructor.calledOnce).toEqual(true);
// expect(1).toEqual(1);
});
});
Now when I run this I get the following error
TypeError: Cannot read property 'restore' of undefined
What am I doing wrong here?
You have two problems here.
The immediate problem of this referring to something else than what you believe it to.
You misunderstand what the constructor property refers to.
The this pointer in afterEach refers to another context (the outer describe context) than the this in the test. Generally, to make life simpler, avoid using this when not necessary and simply use normal variables.
To fix the first problem, let's just rework your example a bit:
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { IncrementalSearch } from './IncrementalSearch';
describe('<IncrementalSearch />', () => {
let constructorSpy;
afterEach(() => {
constructorSpy.restore();
});
it('calls constructor', () => {
constructorSpy = sinon.spy(IncrementalSearch, 'constructor');
const wrapper = mount(<IncrementalSearch />);
expect(IncrementalSearch.prototype.constructor.calledOnce).toEqual(true);
});
});
Your test will still fail, but for another reason
You should read this thread on the Sinon issue tracker, especially the discussion between me and machineghost. The quick summary in your regard is that you need to read up on how Javascript protypes actually work, as the constructor property does not have anything to do with the constructor keyword used in ES6 "classes." It refers to the function that created the object. Changing it after the fact, will not have any effect.
The actual constructor is the function ("class") you export from ./IncrementalSearch. Just see how babel transpiles it. So, in essence, if you really want this test to pass, which has nothing to do with your code, but is just a check if React is actually calling your code (which you should really just trust it does...), it should suffice to wrap the function. I haven't tested this though, as this might fail due to the spy not calling the wrapped function with new.

Apply global variable to Vuejs

I have a javascript variable which I want to pass globally to Vue components upon instantiation thus either each registered component has it as a property or it can be accessed globally.
Note:: I need to set this global variable for vuejs as a READ ONLY property
Just Adding Instance Properties
vue2
For example, all components can access a global appName, you just write one line code:
Vue.prototype.$appName = 'My App'
Define that in your app.js file and IF you use the $ sign be sure to use it in your template as well: {{ $appName }}
vue3
app.config.globalProperties.$http = axios.create({ /* ... */ })
$ isn't magic, it's a convention Vue uses for properties that are available to all instances.
Alternatively, you can write a plugin that includes all global methods or properties. See the other answers as well and find the solution that suits best to your requirements (mixin, store, ...)
You can use a Global Mixin to affect every Vue instance. You can add data to this mixin, making a value/values available to all vue components.
To make that value Read Only, you can use the method described in this Stack Overflow answer.
Here is an example:
// This is a global mixin, it is applied to every vue instance.
// Mixins must be instantiated *before* your call to new Vue(...)
Vue.mixin({
data: function() {
return {
get globalReadOnlyProperty() {
return "Can't change me!";
}
}
}
})
Vue.component('child', {
template: "<div>In Child: {{globalReadOnlyProperty}}</div>"
});
new Vue({
el: '#app',
created: function() {
this.globalReadOnlyProperty = "This won't change it";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalReadOnlyProperty}}
<child></child>
</div>
In VueJS 3 with createApp() you can use app.config.globalProperties
Like this:
const app = createApp(App);
app.config.globalProperties.foo = 'bar';
app.use(store).use(router).mount('#app');
and call your variable like this:
app.component('child-component', {
mounted() {
console.log(this.foo) // 'bar'
}
})
doc: https://v3.vuejs.org/api/application-config.html#warnhandler
If your data is reactive, you may want to use VueX.
You can use mixin and change var in something like this.
// This is a global mixin, it is applied to every vue instance
Vue.mixin({
data: function() {
return {
globalVar:'global'
}
}
})
Vue.component('child', {
template: "<div>In Child: {{globalVar}}</div>"
});
new Vue({
el: '#app',
created: function() {
this.globalVar = "It's will change global var";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalVar}}
<child></child>
</div>
If the global variable should not be written to by anything, including Vuejs, you can use Object.freeze to freeze your object. Adding it to Vue's viewmodel won't unfreeze it.
Another option is to provide Vuejs with a frozen copy of the object, if the object is intended to be written globally but just not by Vue: var frozenCopy = Object.freeze(Object.assign({}, globalObject))
you can use Vuex to handle all your global data
In your main.js file, you have to import Vue like this :
import Vue from 'vue'
Then you have to declare your global variable in the main.js file like this :
Vue.prototype.$actionButton = 'Not Approved'
If you want to change the value of the global variable from another component, you can do it like this :
Vue.prototype.$actionButton = 'approved'
https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
If you’d like to use a variable in many components, but you don’t want to pollute the global scope. In these cases, you can make them available to each Vue instance by defining them on the Vue prototype:
Vue.prototype.$yourVariable = 'Your Variable'
Please remember to add this line before creating your Vue instance in your project entry point, most of time it's main.js
Now $yourVariable is available on all Vue instances, even before creation. If we run:
new Vue({
beforeCreate: function() {
console.log(this.$yourVariable)
}
})
Then "Your Variable" will be logged to the console!
doc: https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
If you want to make this variable immutable, you can use the static method Object.defineProperty():
Object.defineProperty(Vue.prototype, '$yourVariable', {
get() {
return "Your immutable variable"
}
})
This method by default will prevent your variable from being removed or replaced from the Vue prototype
If you want to take it a step further, let's say your variable is an object, and you don't want any changes applied to your object, you can use Object.freeze():
Object.defineProperty(Vue.prototype, '$yourVariable', {
get() {
return Object.freeze(yourGlobalImmutableObject)
}
})
A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY. I did like that:
Supposing that I have 2 global variables (var1 and var2). Just add to the index.html header this code:
<script>
function getVar1() {
return 123;
}
function getVar2() {
return 456;
}
function getGlobal(varName) {
switch (varName) {
case 'var1': return 123;
case 'var2': return 456;
// ...
default: return 'unknown'
}
}
</script>
It's possible to do a method for each variable or use one single method with a parameter.
This solution works between different vuejs mixins, it a really global value.
in main.js (or any other js file)
export const variale ='someting'
in app.vue (or any other component)
import {key} from '../main.js' (file location)
define the key to a variable in data method and use it.
Simply define it in vite configuration
export default defineConfig({
root:'/var/www/html/a1.biz/admin',
define: {
appSubURL: JSON.stringify('/admin')
}, ..../// your other configurations
});
Now appSubURL will be accessible everywhere

How to get createContainer & Collection working outside of main jsx

I'm a novice to Meteor/React.js applications, and while I've been able to cobble together an app with multiple components by passing the App.jsx collections around as props, I can't seem to get individual components to handle their own collections. My rational is that some components need the collection fetched in one order, while others need the collection fetched in another. It seems silly to pass around a collection and then have to manipulate it internally to get the proper effect when a new sort is what's needed. My main App.jsx has:
App.propTypes = {
workdone:PropTypes.array.isRequired,
subjects:PropTypes.array.isRequired
};
export default createContainer(() => {
return {
workdone: WorkDoneCollection.find({},{sort:{createdAt:-1}}).fetch(),
subjects: SubjectCollection.find({},{sort:{subject:1}}).fetch()
};
}, App);
and if I pass the props.workdone on to another component everything works fine. On the other hand if I try a set up an independent collection in the other component, like this:
WorkSummary.propTypes = {
workdone:PropTypes.array
};
export default createContainer(() => {
return {
workdone: WorkDoneCollection.find({},{sort:{createdAt:1}}).fetch()
};
}, WorkSummary);
the workdone property is undefined.

How to Two-way Data Binding Between Parents and grandchildren in Vue.js

I faced a problem, I solve it by cookies but I want to solve the problem without cookies. I have a component which called app-header and It has another component which called outmodal.
Now, My first Vue instance require component app-header.
var vue = new Vue({
el : "html",
data : {
title : "Site Title",
description : "description of page",
keywords : "my keywords",
view : "home",
login : "login"
},
components:{
"app-header" :require("../../components/header"),
"app-footer" :require("../../components/footer"),
"home" :require("../../views/home")
},
});
code of app-header
var Vue = require("vue");
Vue.partial("login",require("../../partials/login.html"));
Vue.partial("logged",require("../../partials/logged.html"));
module.exports = {
template : require("./template.html"),
replace : true,
components : {
outmodal : require("../outmodal")
},
props : ['login']
}
code of outmodal
var Vue = require("vue");
Vue.partial("loginModal",require("../../partials/loginModal.html"));
module.exports = {
template : require("./template.html"),
replace : true,
props : ['name'],
data : function () {
return {
userLogin : { mail : "", password : "", remember : ""}
}
},
methods : {
formSubmit : function(e){
e.preventDefault();
this.$http.post("http://example.com/auth/login",{ "email": this.userLogin.mail , "password": this.userLogin.password },function(data,status,request){
$.cookie("site_token",data.token,{expires : 1})
}).error(function(data,status,request){
});
}
}, ready : function(){
console.log("it works")
}
}
In outmodal component I connect the API and I check the login, If login will be succesfull, I want to change value of login variable in my Vue instance. I use web pack to build all requires. So I don't know how can I data binding between these files.
How can I solve It? I
The Best Solution which I found
For 0.12
http://012.vuejs.org/guide/components.html#Inheriting_Parent_Scope
for 1.0
http://v1.vuejs.org/guide/components.html#Parent-Child-Communication
for 2.0
https://v2.vuejs.org/v2/guide/components.html#Composing-Components (use props to one-way bind data from parent to child)
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.
i found this one to be more accurate.
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
only in 2.3.0+ tho.
and honestly it's still not good enough. should simply be a easy option for 'two-way' data binding. so none of these options is good.
try using vuex instead. they have more options for such purpose.
https://vuex.vuejs.org/en/state.html
I would prefer event-driven updates as recommended in the documentation. However, I was limited by the existing ("third-party") component already using props and $emit. This component is my grandchild. The following is my solution (passing value through child using props, sync and computed value with $emit.
Comments are welcome.
Value can be modified in parent and grandchild without error:
Grandchild (simplified third-party component):
<template>
<div v-show="value">{{ value}}</div>
<button #click="closeBox">Close</button>
</template>
<script>
export default {
props: {
value: null
},
methods: {
closeBox() {
this.$emit('update:value', null);
}
}
}
</script>
Child:
<template>
<grandchild-component :value.sync="passedValue" />
</template>
<script>
export default {
props: {
value: null
},
computed: {
passedValue: {
get() {
return this.value;
},
set(newVal) {
this.$emit('update:value', newVal);
}
}
}
}
</script>
Parent:
<template>
<child-component :value.sync="value" />
</template>
<script>
export default {
data() {
return {
value: null,
}
},
// ... e.g. method setting/modifying the value
}
</script>

Resources