Vue.js - How to Avoid Code Duplication - vue.js

How does one avoid code duplication when using something like computed properties across multiple components?
An example is that I have a computed property to get the parent route name - it's pretty simple:
computed: {
parent_route () {
return this.$route.matched[0].name
}
}
I am finding that I use this computed property across multiple components. How could I store this in one place that all my components can use?

You can use Mixins. For the most general case there's the Global Mixin (use with care):
Vue.mixin({
computed: {
parent_route () {
return this.$route.matched[0].name
}
}
})
The this.parent_route computed property is now automatically defined in all components.
But you should avoid abusing global mixins. Instead, you can apply them locally using Option Merging (the mixins option):
var mixin = {
computed: {
parent_route () {
return this.$route.matched[0].name
}
}
};
new Vue({
mixins: [mixin],
data: function () {},
created: function () {
console.log(this.parent_route) // should be ok
}
})

You can use mixins. Create myMixin.js file with:
export const myMixin = {
computed: {
parent_route () {
return this.$route.matched[0].name
}
}
}
Then you can import it in your vue component like this:
import {myMixin} from '../myMixin.js' //valid path to myMixin.js file here
and register it:
...
data {...},
mixins: [myMixin],
methods: {...}
...
More on mixins:
https://v2.vuejs.org/v2/guide/mixins.html

Related

vue mixin shared between instance and component

I have some functionality that I'd like to share throughout my site. My site does have multiple Vue instances, and not just one single app instance entry point.
I've created my mixin like this:
var fooMixin = {
data: {
someProperty: null,
someOtherProperty: "Foo"
},
methods{
...
}
}
This mixin is then injected into my Vue instance like so:
new Vue({
el: '#app',
mixins: [fooMixin],
data: {
...
},
methods: {
...
}
})
Of course this works exactly as intended, but my issue is that I want to reuse this mixin within a component elsewhere. This causes issues.
Here's how it's injected into the component:
Vue.component('bar', {
props: ['someProp'],
template: barTemplate,
mixins: [fooMixin],
data: function () {
return {
mySpecialProperty: null
}
},
methods: {
...
}
})
As you can imagine, the mixin cannot be merged with the data property of the components. Since this is a component, the data property must return a function that return the object. This is not how my mixin has been set up.
This is the error I'm given from Vue:
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
Can I create a mixin that is reusable across instances and components?
Change your data property in mixin from object to function
var mixin = {
data: function () {
return {
someProperty: null,
someOtherProperty: 'foo'
}
}
}

Vuex: createNamespacedHelpers with dynamic namespace

In almost all guides, tutorial, posts, etc that I have seen on vuex module registration, if the module is registered by the component the createNamespacedHelpers are imported and defined prior to the export default component statement, e.g.:
import {createNamespacedHelpers} from 'vuex'
const {mapState} = createNamespacedHelpers('mymod')
import module from '#/store/modules/mymod'
export default {
beforeCreated() {
this.$store.registerModule('mymod', module)
}
}
this works as expected, but what if we want the module to have a unique or user defined namespace?
import {createNamespacedHelpers} from 'vuex'
import module from '#/store/modules/mymod'
export default {
props: { namespace: 'mymod' },
beforeCreated() {
const ns = this.$options.propData.namespace
this.$store.registerModule(ns, module)
const {mapState} = createNamespacedHelpers(ns)
this.$options.computed = {
...mapState(['testVar'])
}
}
}
I thought this would work, but it doesnt.
Why is something like this needed?
because
export default {
...
computed: {
...mapState(this.namespace, ['testVar']),
...
},
...
}
doesnt work
This style of work around by utilising beforeCreate to access the variables you want should work, I did this from the props passed into your component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above
}
}
Hope you find this useful.
All posted answers are just workarounds leading to a code that feels verbose and way away from standard code people are used to when dealing with stores.
So I just wanted to let everyone know that brophdawg11 (one of the commenters on the issue #863) created (and open sourced) set of mapInstanceXXX helpers aiming to solve this issue.
There is also series of 3 blog posts explaining reasons behind. Good read...
I found this from veux github issue, it seems to meet your needs
https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
{
props: ['namespace'],
computed: mapState({
state (state) {
return state[this.namespace]
},
someGetter (state, getters) {
return getters[this.namespace + '/someGetter']
}
}),
methods: {
...mapActions({
someAction (dispatch, payload) {
return dispatch(this.namespace + '/someAction', payload)
}
}),
...mapMutations({
someMutation (commit, payload) {
return commit(this.namespace + '/someMutation', payload)
})
})
}
}
... or maybe we don't need mapXXX helpers,
mentioned by this comment https://github.com/vuejs/vuex/issues/863#issuecomment-439039257
computed: {
state () {
return this.$store.state[this.namespace]
},
someGetter () {
return this.$store.getters[this.namespace + '/someGetter']
}
},

vuejs 2 how to watch store values from vuex when params are used

How can I watch for store values changes when params are used? I normally would do that via a getter, but my getter accepts a param which makes it tricky as I've failed to find documentation on this scenario or a stack Q/A.
(code is minimized for demo reasons)
My store.js :
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
let report = {
results: [],
};
export const store = new Vuex.Store({
state: {
broken: Object.assign({}, report),
},
results: (state) => (scan) => {
return state[scan].results
},
});
vue-component.vue :
computed: {
...mapGetters([
'results',
]),
watch: {
results(){ // How to pass the param ??
// my callback
}
So basically I would like to find out how to pass the param so my watch would work.
In my opinion, there is no direct solution for your question.
At first, for watch function, it only accept two parameters, newValue and oldValue, so there is no way to pass your scan parameter.
Also, your results property in computed, just return a function, if you watch the function, it will never be triggered.
I suggest you just change the getters from nested function to simple function.
But if you really want to do in this way, you should create a bridge computed properties
computed: {
...mapGetters([
'results',
]),
scan() {
},
mutatedResults() {
return this.results(this.scan);
},
watch: {
mutatedResults() {
}
}
}

Component without template

I have a bit of code that makes an api call to a server and returns some JSON.
It did exist as a method in my component but as it is getting a bit long I want to extract it to it's own file
In vuejs what is the best practice here.
should it be a component without a template? How would this work?
will I just create an es6 module?
I would suggest using a mixin here.
In a file like myCoolMixin.js define your mixin...
export default {
methods: {
myAwesomeMethod() {
//do something cool...
}
}
}
You can define anything in a mixin just like a component. e.g. data object, computed or watched properties, etc. Then you simply include the mixin in your component.
import myCoolMixin from '../path/to/myCoolMixin.js'
export default {
mixins: [myCoolMixin],
data: function() {
return: {
//...
}
},
mounted: function() {
this.myAwesomeMethod(); // Use your method like this!
}
}
More on Mixins here: https://v2.vuejs.org/v2/guide/mixins.html
Mixins work, or you could create a plugin. Here's the docs example:
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
Vue Plugins

mapState with setter

I would like to assign setter methods via mapState. I currently use a workaround where I name the variable that I am interested in (todo) as a temporary name (storetodo) and then refer to it in another computed variable todo.
methods: {
...mapMutations([
'clearTodo',
'updateTodo'
])
},
computed: {
...mapState({
storetodo: state => state.todos.todo
}),
todo: {
get () { return this.storetodo},
set (value) { this.updateTodo(value) }
}
}
I would like to skip the extra step and define the getter, setter directly within mapState.
Why would I want to do this?
The normal approach would be use mapMutations/mapActions & mapState/mapGetters
without the computed get/set combination that I have illustrated above and to reference the mutation directly in the HTML:
<input v-model='todo' v-on:keyup.stop='updateTodo($event.target.value)' />
The getter/setter version allows me to simply write:
<input v-model='todo' />
You can't use a getter/setter format in the mapState
what you can try is directly return the state in your get() and remove mapState from the computed property
computed: {
todo: {
get () { return this.$store.state.todos.todo},
set (value) { this.updateTodo(value) }
}
}
Here is a related but not same JsFiddle example
This is my current workaround. Copied from my personal working project
// in some utils/vuex.js file
export const mapSetter = (state, setters = {}) => (
Object.keys(state).reduce((acc, stateName) => {
acc[stateName] = {
get: state[stateName],
};
// check if setter exists
if (setters[stateName]) {
acc[stateName].set = setters[stateName];
}
return acc;
}, {})
);
In your component.vue file
import { mapSetter } from 'path/to/utils/vuex.js';
export default {
name: 'ComponentName',
computed: {
...mapSetter(
mapState({
result: ({ ITEMS }) => ITEMS.result,
total: ({ ITEMS }) => ITEMS.total,
current: ({ ITEMS }) => ITEMS.page,
limit: ({ ITEMS }) => ITEMS.limit,
}),
{
limit(payload) {
this.$store.dispatch({ type: TYPES.SET_LIMIT, payload });
},
},
)
},
}
now you can use the v-model bindings. l
Another way of approaching that is using store mutations like below:
//in your component js file:
this.$store.commit('setStoretodo', storetodo)
Assuming you define setStoretodo in mutations of your vuex store instance (which is something recommended to have anyways):
//in your vuex store js file:
state:{...},
actions: {...}
...
mutations: {
setStoretodo(state, val){
state.storetodo = val
},
...
}
...
That keeps the property reactive as mapState will grab the updated value and it will be rendered automatically.
Surely, that's not as cool as just writing this.storetodo = newValue, but maybe someone will find that helpful as well.