Separating vuex stores for dynamically created components - javascript

This was the question got me stuck for a little bit. Unfortunately, I coudn't find answer here (asking also didn't help). So after doing some research and asking here and there, it seems that I got the solution to this issue.
If you have a question that you already know the answer to, and you
would like to document that knowledge in public so that others
(including yourself) can find it later.
Of course, my answer may not be the ideal one, moreover I know it is not, that's the key point why I'm posting - to improve it.
Note, I'm not using actions in example. The idea is the same.
Let's begin with stating the problem:
Imagine we have App.vue which dynamically generates its local component named Hello.
<template>
<div id="app">
<div>
<hello v-for="i in jobs" :key="i" :id="i"></hello>
<button #click="addJob">New</button>
</div>
</div>
</template>
<script>
import Hello from './components/Hello'
export default {
components: {
Hello
}...
store.js
export const store = new Vuex.Store({
state: {
jobs: []
}
})
We are using v-for directive to generate components by iterating through an array jobs. Our store as of now consists of only state with an empty array.
Button New should do 2 things:
1) create new component Hello, in other words add element to jobs (let it be numbers), which are going to be assigned as key and id of <hello>, and passed to local component as props.
2) generate local stores - modules - to keep any data scoped to newly created components.
Hello.vue
<template>
<div>
<input type="number" :value="count">
<button #click="updateCountPlus">+1</button>
</div>
</template>
export default {
props: ['id']
}
Simple component - input with a button adding 1.
Our goal is to design something like this:

For the first operation of NEW button - generating components - we add mutation to our store.js
mutations: {
addJob (state) {
state.jobs.push(state.jobs.length + 1)
...
}
Second, creating local modules. Here we're going to use reusableModule to generated multiple instances of a module. That module we keep in separate file for convinience. Also, note use of function for declaring module state.
const state = () => {
return {
count: 0
}
}
const getters = {
count: (state) => state.count
}
const mutations = {
updateCountPlus (state) {
state.count++
}
}
export default {
state,
getters,
mutations
}
To use reusableModule we import it and apply dynamic module registration.
store.js
import module from './reusableModule'
const {state: stateModule, getters, mutations} = module
export const store = new Vuex.Store({
state: {
jobs: []
},
mutations: {
addJob (state) {
state.jobs.push(state.jobs.length + 1)
store.registerModule(`module${state.jobs.length}`, {
state: stateModule,
getters,
mutations,
namespaced: true // making our module reusable
})
}
}
})
After, we're going to link Hello.vue with its storage. We may need state, getters, mutations, actions from vuex. To access storage we need to create our getters. Same with mutations.
Home.vue
<script>
export default {
props: ['id'],
computed: {
count () {
return this.$store.getters[`module${this.id}/count`]
}
},
methods: {
updateCountPlus () {
this.$store.commit(`module${this.id}/updateCountPlus`)
}
}
}
</script>
Imagine we have lots of getters, mutations and actions. Why not use {mapGetters} or {mapMutations}? When we have several modules and we know the path to module needed, we can do it. Unfortunately, we do not have access to module name.
The code is run when the component's module is executed (when your app
is booting), not when the component is created. So these helpers can
only be used if you know the module name ahead of time.
There is little help here. We can separate our getters and mutations and then import them as an object and keep it clean.
<script>
import computed from '../store/moduleGetters'
import methods from '../store/moduleMutations'
export default {
props: ['id'],
computed,
methods
}
</script>
Returning to App component. We have to commit our mutation and also let's create some getter for App. To show how can we access data located into modules.
store.js
export const store = new Vuex.Store({
state: {
jobs: []
},
getters: {
jobs: state => state.jobs,
sumAll (state, getters) {
let s = 0
for (let i = 1; i <= state.jobs.length; i++) {
s += getters[`module${i}/count`]
}
return s
}
}
...
Finishing code in App component
<script>
import Hello from './components/Hello'
import {mapMutations, mapGetters} from 'vuex'
export default {
components: {
Hello
},
computed: {
...mapGetters([
'jobs',
'sumAll'
])
},
methods: {
...mapMutations([
'addJob'
])
}
}
</script>

Hi and thank you for posting your question and your solution.
I started learning Vuex couple days ago and came across a similar problem. I've checked your solution and came up with mine which doesn't require registering new modules. I find it to be quite an overkill and to be honest I don't understand why you do it. There is always a possibility I've misunderstood the problem.
I've created a copy of your markup with a few differences for clarity and demonstration purposes.
I've got:
JobList.vue - main custom component
Job.vue - job-list child custom component
jobs.js - vuex store module file
JobList.vue (which is responsible for wrapping the job(s) list items)
<template>
<div>
<job v-for="(job, index) in jobs" :data="job" :key="job.id"></job>
<h3>Create New Job</h3>
<form #submit.prevent="addJob">
<input type="text" v-model="newJobName" required>
<button type="submit">Add Job</button>
</form>
</div>
</template>
<script>
import store from '../store/index'
import job from './job';
export default {
components: { job },
data() {
return {
newJobName: ''
};
},
computed: {
jobs() {
return store.state.jobs.jobs;
}
},
methods: {
addJob() {
store.dispatch('newJob', this.newJobName);
}
}
}
</script>
The Job
<template>
<div>
<h5>Id: {{ data.id }}</h5>
<h4>{{ data.name }}</h4>
<p>{{ data.active}}</p>
<button type="button" #click="toggleJobState">Toggle</button>
<hr>
</div>
</template>
<script>
import store from '../store/index'
export default {
props: ['data'],
methods: {
toggleJobState() {
store.dispatch('toggleJobState', this.data.id);
}
}
}
</script>
And finally the jobs.js Vuex module file:
export default {
state: {
jobs: [
{
id: 1,
name: 'light',
active: false
},
{
id: 2,
name: 'medium',
active: false
},
{
id: 3,
name: 'heavy',
active: false
}
]
},
actions: { //methods
newJob(context, jobName) {
context.state.jobs.push({
id: context.getters.newJobId,
name: jobName,
active: false
});
},
toggleJobState(context, id) {
context.state.jobs.forEach((job) => {
if(job.id === id) { job.active = !job.active; }
})
}
},
getters: { //computed properties
newJobId(state) { return state.jobs.length + 1; }
}
}
It's possible to add new jobs to the store and as the "active" property suggest, you can control every single individual job without the need for a new custom vuex module.

Related

how to prevent or stop parent component to re-render in vuejs

Hi Guys i'm trying to create a look something like Bootstrap Nav Tabs but with Vuejs and Vue Router i also want to change the url in browser
here is my code for VueRouter
it is working fine but the Parent component(UserProfile) get re-render every time i switch between UserProfilePosts or UserDetails because i know my code going to be larger and this is not a good user experience,Thanks
{
path:'/:id',
component:UserProfile,
children: [
{ path: '', component: UserProfilePosts },
{ path: 'details', component: UserDetails },
],
meta:{
requiresAuth:true
}
}
Main Component(UserProfile):
<template>
<div class="container-fluid">
<h1>UserProfile</h1>
<router-link to="/username">Post's</router-link>
<router-link to="/username/details">Details</router-link>
<router-view></router-view>
</div>
<script>
export default{
created(){
console.log('created');
}
}</script>
You can try using Vuex with vex-persist. Vue refresh and reload the html each time it is asked. I am new to vue and this was how I implemented it, though it may not be the best solution.
VueX is the one central source of truth that your components can look for information. It will be easier passing down as prop and all the components just head to the 'store' for information
This stores the information as a local / session storage. For more information check out : https://github.com/championswimmer/vuex-persistuex-persist
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
const vuexLocal = new VuexPersistence({
storage: window.sessionStorage
})
Vue.use(Vuex)
export default new Vuex.Store({
plugins: [vuexLocal.plugin],
state: {
database: []
},
mutations: {
pushToDatabase: (state, val) => {
state.database.push (val}
}
},
getters: {
getData: state => {
return state.database
}
}
})

Vue: How to use component prop inside mapFields

I have general component and vuex store. For easy two-way binding I use vuex-map-fields. On component side it has mapFields method which creates get&set with mutations.
I want to pass namespace from vuex module with props but it seems to be impossible.
<my-component namespace="ns1" />
// my-component code
export default {
props: ["namespace"],
computed: {
...mapFields(??this.namespace??, ["attr1", "attr2"])
}
}
Of course, there is no way to use this in such way so we don't have access to props. How can I specify namespace as prop in such case?
The problem (as you probably gathered) is that computed properties are constructed before this is available, but you can get around it by deferring resolution of the this.namespace property until the computed property is called (which won't happen until component construction is finished).
The concept is based on this post Generating computed properties on the fly.
The basic pattern is to use a computed with get() and set()
computed: {
foo: {
get() { this.namespace...},
set() { this.namespace...},
}
}
but rather than type it all out in the component we can create a helper function based on the vuex-map-fields mapFields() function (see here for the original).
The normalizeNamespace() function that comes with vuex-map-fields does not support what we want to do, so we drop it and assume the namespace is always passed in (and that the store module uses the standard getField and updateField functions).
I have adapted one of the vuex-map-fields Codesandbox examples here.
Note the namespace is in data rather than props for conveniance, but props should work also.
Template
<template>
<div id="app">
<div>
<label>foo </label> <input v-model="foo" /> <span> {{ foo }}</span>
</div>
<br />
<div>
<label>bar </label> <input v-model="bar" /> <span> {{ bar }}</span>
</div>
</div>
</template>
Helper
<script>
const mapFields2 = (namespaceProp, fields) => {
return Object.keys(fields).reduce((prev, key) => {
const path = fields[key];
const field = {
get() {
const namespace = this[namespaceProp];
const getterPath = `${namespace}/getField`;
return this.$store.getters[getterPath](path);
},
set(value) {
const namespace = this[namespaceProp];
const mutationPath = `${namespace}/updateField`;
this.$store.commit(mutationPath, { path, value });
}
};
prev[key] = field;
return prev;
}, {});
};
export default {
name: "App",
data() {
return {
nsProp: "fooModule"
};
},
computed: {
...mapFields2("nsProp", { foo: "foo", bar: "bar" })
}
};
</script>
Store
import Vue from "vue";
import Vuex from "vuex";
import { getField, updateField } from "vuex-map-fields";
import App from "./App";
Vue.use(Vuex);
Vue.config.productionTip = false;
const store = new Vuex.Store({
modules: {
fooModule: {
namespaced: true,
state: {
foo: "initial foo value",
bar: "initail bar value"
},
getters: {
getField
},
mutations: {
updateField
}
}
}
});
new Vue({
el: "#app",
components: { App },
store,
template: "<App/>"
});

#JSON to #Vuex store

I am new to Vuex store which is served by vue.js. and i want to use it in following scenerio.
1.Does STATE means any data which is either static or dynamic which is served by server. or say data stored in json?
TEMPLATE.
<!-- title start -->
<div class="_Handler-0-1">
<x-x :path="store.glyph.path['0']":classs="store.static.class['0']"/>
</div>
<!-- title end -->
OBJECT
store: {
glyph: {
path: {
0: '<svg>.....</svg'>
}
},
static: {
class: {
0: 'icon-phone'
}
}
}
It's worth reading the documentation on vuex.
https://vuex.vuejs.org/en/intro.html
It's all about state management. You can retrieve data from the server and store it in there if you want to or you can store user entered data. It's very flexible, but the way it is designed is to ensure that it is always managed correctly
Vuex' has a functional life-cycle :
Dispatchers
Actions
Mutations
Getters
Dispatchers .dispatch Actions
Actions commit Mutations
Mutations mutate (change) the state
Getters return parts of the state.
I don't know the full setup of how you've done your store but to retrieve the two parts of your state i would write a getter that returns both parts.
const store = new Vuex.Store({
state: {
glyph: {
path: '<svg>.....</svg>'
},
static: {
class: 'icon-phone'
}
},
getters: {
glyph: state => state.glyph,
static: state => state.static
}
})
<template>
<div class="_Handler-0-1">
<x-x :path="glyph.path":class="static.path"/>
</div>
</template>
<script>
import { ...mapGetters } from 'vuex'
export default {
name: 'foo',
computed: {
...mapGetters(['glyph', 'static'])
}
}
</script>
Also worth mentioning that static is a reserved word.

Vue.js how to expose component data properties for use in third party script?

I am wondering what the cleanest way to expose data properties on a single-file Vue component without polluting the global namespace is.
In my main entry file(app.js) for Vue I am setting up Vue like so:
import components from './components/components';
window.app = new Vue({
el: '#vue',
store,
// etc.
});
My components.js imports all the components that I want to use as HTML snippets. Some of these components are import components on their own that are not directly set as components on my root instance.
What would be the best way to expose some data properties of certain single-file Vue components?
For instance I have a Search.vue component where I would like to send my first 3 objects from an array of results to Google Analytics:
// Expose this on `Search.vue`:
data: {
// Filled with data from ajax request.
results: []
}
My Vue instance is available globally. Is there an easy way to access a certain component perhaps by name or something?
Edit
So far the best option currently looks like accessing my property(which I have available inside my store) through a getter like so:
this.$store.getters.propertyINeed
Any suggestions on how to improve on this are welcome.
I suggest you store the data you need in a Vuex store. As you can see, the srch-component has a computed property which gives the result and there's a watcher that will automatically dispatch the data to the store. Then you can use something like app.$store to access the data without tampering the components.
Note that you can also better manage the store using modules (link).
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
topSrchResult: []
},
mutations: {
updateTopSrchResult: (state, payload) => {
state.topSrchResult = payload
}
},
actions: {
UPDATE_TOP_SRCH_RESULT: ({ commit }, data) => {
commit('updateTopSrchResult', data)
}
}
})
Vue.component('srch-component', {
template: `
<div>
<div>Input: <input v-model="inputVal" type="text"/></div>
<div>Search Results:</div>
<ul>
<li v-for="item in srchResult">{{item}}</li>
</ul>
</div>
`,
data() {
return {
inputVal: '',
dummyData: [
'Scrubs', 'Hannah Montana', '30 Rock', 'Wizards of Waverly Place',
'How I Met Your Mother', 'Community', 'South Park', 'Parks and Recreation',
'The Office', 'Brooklyn Nine-Nine', 'Simpsons', 'Fringe', 'Chuck'
]
}
},
watch: {
srchResult() {
const payload = this.srchResult.length <= 3 ? this.srchResult : this.srchResult.slice(0,3)
this.$store.dispatch('UPDATE_TOP_SRCH_RESULT', payload)
}
},
computed: {
srchResult() {
return this.dummyData
.filter(
(item) => item.toLowerCase().includes(this.inputVal.toLowerCase())
)
}
}
})
const app = new Vue({
el: '#app',
computed: {
topSrchResult() {
return this.$store.state.topSrchResult
}
},
store
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.1/vuex.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="app">
<div>Top Results</div>
<ul>
<li v-for="item in topSrchResult">{{item}}</li>
</ul>
<srch-component></srch-component>
</div>

How to avoid the need of writing this.$store.state.donkey all the time in Vue?

I'm learning Vue and I noticed that I have the following syntax more or less everywhere.
export default {
components: { Navigation, View1 },
computed: {
classObject: function() {
return {
alert: this.$store.state.environment !== "dev",
info: this.$store.state.environment === "dev"
};
}
}
}
It's a pain to write out this.$store.state.donkey all the time and it lowers the readability too. I'm sensing that I'm doing it in a less than optimal way. How should I refer to the state of the store?
you can set computed properties for both states & getters i.e.
computed: {
donkey () {
this.$store.state.donkey
},
ass () {
this.$store.getters.ass
},
...
Whilst you still need to call the $state.store once you can then reference a donkey or an ass on your vm...
To make things even easier you can pull in the vuex map helpers and use them to find your ass ... or donkey:
import { mapState, mapGetters } from 'vuex'
default export {
computed: {
...mapState([
'donkey',
]),
...mapGetters([
'ass',
]),
...mapGetters({
isMyAss: 'ass', // you can also rename your states / getters for this component
}),
now if you look at this.isMyAss you'll find it ... your ass
worth noting here that getters, mutations & actions are global - therefore they are referenced directly on your store, i.e. store.getters, store.commit & store.dispatch respectively. This applies whether they are in a module or in the root of your store. If they are in a module check out namespacing to prevent overwriting previously used names: vuex docs namespacing. However if you are referencing a modules state, you must prepend the name of the module, i.e. store.state.user.firstName in this example user is a module.
Edit 23/05/17
Since the time of writing Vuex has been updated and its namespacing feature is now a go to when you work with modules. Simply add namespace: true to your modules export, i.e.
# vuex/modules/foo.js
export default {
namespace: true,
state: {
some: 'thing',
...
add the foo module to your vuex store:
# vuex/store.js
import foo from './modules/foo'
export default new Vuex.Store({
modules: {
foo,
...
then when you are pulling this module into your components you can:
export default {
computed: {
...mapState('foo', [
'some',
]),
...mapState('foo', {
another: 'some',
}),
...
this makes modules very simple and clean to use, and is a real saviour if you are nesting them multiple levels deep: namespacing vuex docs
I have put together an example fiddle to showcase the various ways you can reference and work with your vuex store:
JSFiddle Vuex Example
Or check out the below:
const userModule = {
state: {
firstName: '',
surname: '',
loggedIn: false,
},
// #params state, getters, rootstate
getters: {
fullName: (state, getters, rootState) => {
return `${state.firstName} ${state.surname}`
},
userGreeting: (state, getters, rootState) => {
return state.loggedIn ? `${rootState.greeting} ${getters.fullName}` : 'Anonymous'
},
},
// #params state
mutations: {
logIn: state => {
state.loggedIn = true
},
setName: (state, payload) => {
state.firstName = payload.firstName
state.surname = payload.surname
},
},
// #params context
// context.state, context.getters, context.commit (mutations), context.dispatch (actions)
actions: {
authenticateUser: (context, payload) => {
if (!context.state.loggedIn) {
window.setTimeout(() => {
context.commit('logIn')
context.commit('setName', payload)
}, 500)
}
},
},
}
const store = new Vuex.Store({
state: {
greeting: 'Welcome ...',
},
mutations: {
updateGreeting: (state, payload) => {
state.greeting = payload.message
},
},
modules: {
user: userModule,
},
})
Vue.component('vuex-demo', {
data () {
return {
userFirstName: '',
userSurname: '',
}
},
computed: {
loggedInState () {
// access a modules state
return this.$store.state.user.loggedIn
},
...Vuex.mapState([
'greeting',
]),
// access modules state (not global so prepend the module name)
...Vuex.mapState({
firstName: state => state.user.firstName,
surname: state => state.user.surname,
}),
...Vuex.mapGetters([
'fullName',
]),
...Vuex.mapGetters({
welcomeMessage: 'userGreeting',
}),
},
methods: {
logInUser () {
this.authenticateUser({
firstName: this.userFirstName,
surname: this.userSurname,
})
},
// pass an array to reference the vuex store methods
...Vuex.mapMutations([
'updateGreeting',
]),
// pass an object to rename
...Vuex.mapActions([
'authenticateUser',
]),
}
})
const app = new Vue({
el: '#app',
store,
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<!-- inlining the template to make things easier to read - all of below is still held on the component not the root -->
<vuex-demo inline-template>
<div>
<div v-if="loggedInState === false">
<h1>{{ greeting }}</h1>
<div>
<p><label>first name: </label><input type="text" v-model="userFirstName"></p>
<p><label>surname: </label><input type="text" v-model="userSurname"></p>
<button :disabled="!userFirstName || !userSurname" #click="logInUser">sign in</button>
</div>
</div>
<div v-else>
<h1>{{ welcomeMessage }}</h1>
<p>your name is: {{ fullName }}</p>
<p>your firstName is: {{ firstName }}</p>
<p>your surname is: {{ surname }}</p>
<div>
<label>Update your greeting:</label>
<input type="text" #input="updateGreeting({ message: $event.target.value })">
</div>
</div>
</div>
</vuex-demo>
</div>
As you can see if you wanted to pull in mutations or actions this would be done in a similar way but in your methods using mapMutations or mapActions
Adding Mixins
To extend the above behaviour you could couple this with mixins and then you'd only have to set up the above computed properties once and pull in the mixin on the component that needs them:
animals.js (mixin file)
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState([
'donkey',
...
your component
import animalsMixin from './mixins/animals.js'
export default {
mixins: [
animalsMixin,
],
created () {
this.isDonkeyAnAss = this.donkey === this.ass
...

Categories