Quasar - Store Model Bind Pattern - Part I
Tags: vue, quasarframework, quasar, ssr
Published: 2019-08-18 16:59:49

# 1. The problem

A common problem when you're starting with the `Quasar` and your `SSR` mode, is that you are forced to use `preFetch` and dedicated store modules for each `Page`. So even single SFC like that, can become somewhat verbose and tedious.

**src/pages/Person/Index.vue.***

```html
<template>
  <div class="row q-col-gutter-sm">
    <q-input class="col col-6" label="Name" v-model="name" />
    <q-input class="col col-6" label="Surname" v-model="surname" />
  </div>
</template>
<script src="./Index.vue.js"></script>
```
```js
export default {
  data () {
    return {
      name: '',
      surname: ''
    }
  },
  mounted () {
    let { id } = this.$route.params
    // query the person somewhere
  }
}
```

can become something verbose:

**src/store/person.js**

```js
export default {
  namespaced: true,
  state () {
    return {
      name: '',
      surname: ''
    }
  },
  mutations: {
    name (state, value) { state.name = value },
    surname (state, value) { state.surname = value }
  },
  actions: {
    initialize ({ context }) {
      // query the person somewhere
    }
  }
}
```

**src/pages/Person/Index.vue.***

```html
<template>
  <div class="row q-col-gutter-sm">
    <q-input class="col col-6" label="Name" v-model="name" />
    <q-input class="col col-6" label="Surname" v-model="surname" />
  </div>
</template>
<script src="./Index.vue.js"></script>
```
```js
import Module from 'src/store/person'
import { mapActions } from 'vuex'
const moduleName = 'person'
export default {
  preFetch ({ store, currentRoute }) {
    store.registerModule(moduleName, Module)
    return store.dispatch(`${moduleName}/initialize`, currentRoute.params.id)
  },
  mounted () {
    if (!this.$store.state[moduleName]) {
      this.$store.registerModule(moduleName, Module, { preserveState: true })
      this.$store.dispatch(`${moduleName}/initialize`, this.$route.params.id)
    }
  },
  destroyed () {
    this.$store.unregisterModule(moduleName)
  },
  computed: {
    name: {
      get () { return this.$store.state[moduleName].name },
      set (value) { this.$store.commit(`${moduleName}/name`, value) }
    },
    surname: {
      get () { return this.$store.state[moduleName].name },
      set (value) { this.$store.commit(`${moduleName}/name`, value) }
    }
  }
}
```

So, if you want to manage your fields (rename, create or remove), instead of edit your data hook, you'll need to edit the state, the mutation name, the mutation itself, the computed (hook) names, the computed gets and the computed sets.

# 2. Utilities belt

We'll need to create some utility methods, to map the states, mutations and computed properties.

**src/utils/mapper.js**

```js
import Vue from 'vue'

export function createMutations (Model) {
  const keys = Object.keys(new Model())
  const mutations = keys.reduce((mutations, key) => {
    mutations[key] = function (state, value) {
      Vue.set(state, key, value)
    }
    return mutations
  }, {})
  return mutations
}

export const mapState = function (module, properties) {
  var props = {}
  if (Array.isArray(properties)) {
    properties.forEach(property => {
      props[property] = {
        get () {
          return this.$store.state[module][property]
        },
        set (value) {
          this.$store.commit(`${module}/${property}`, value)
        }
      }
    })
  } else {
    Object.keys(properties).forEach(key => {
      var property = properties[key]
      props[key] = {
        get () { return this.$store.state[module][property] },
        set (value) { this.$store.commit(`${module}/${property}`, value) }
      }
    })
  }
  return props
}

export const mapGetters = function (module, properties) {
  var props = {}
  if (Array.isArray(properties)) {
    properties.forEach(property => {
      props[property] = {
        get () {
          return this.$store.getters[`${module}/${property}`]
        },
        set (value) {
          this.$store.commit(`${module}/${property}`, value)
        }
      }
    })
  } else {
    Object.keys(properties).forEach(key => {
      var property = properties[key]
      props[key] = {
        get () { return this.$store.getters[`${module}/${property}`] },
        set (value) { this.$store.commit(`${module}/${property}`, value) }
      }
    })
  }
  return props
}
```

The `createMutations` will map the fields of an Object to an Object structured like the store mutations.

The `mapState` has a signature similar to the original `vuex's mapState`, but that also will map both `state` and `mutation` to a `computed property`.

The `mapGetters` has a signature similar to the original `vuex's mapGetters`, but that also will map both `getter` and `mutation` to a `computed property`.

# 3. Proprosed Solution - Store Model Bind Pattern

Now, instead of defining our `data structure` directly in the `store's state`, we'll create a `Class Model` that will hold.

**src/models/person.js**

```js
export default class Person {
  name = ''
  surname = ''
}
```

Now, let's update our store.:

**src/store/person.js**

```js
import Model from 'src/store/person'
export default {
  namespaced: true,
  state () {
    return new Model()
  },
  mutations: {
    ...createMutations(Model)
  },
  actions: {
    initialize ({ context }) {
      // query the person somewhere
    }
  }
}
```

If you give a further look on the above `store`, you'll notice that is pretty generic, so we can now use that to scaffolding the stores that we'll create in the future.

Now, we need to update the page itself:

**src/pages/Person/Index.vue.js**

```js
import Module from 'src/store/person'
import Model from 'src/models/person'
import { mapState } from 'src/utils/mapper'

const moduleName = 'person'
const keys = Object.keys(new Model())

export default {
  preFetch ({ store, currentRoute }) {
    store.registerModule(moduleName, Module)
    return store.dispatch(`${moduleName}/initialize`, currentRoute.params.id)
  },
  mounted () {
    if (!this.$store.state[moduleName]) {
      this.$store.registerModule(moduleName, Module, { preserveState: true })
      this.$store.dispatch(`${moduleName}/initialize`, this.$route.params.id)
    }
  },
  destroyed () {
    this.$store.unregisterModule(moduleName)
  },
  computed: {
    ...mapState(moduleName, keys)
  }
}
```

The `Page` still looks pretty verbose when compared to the previous version, but like the `store`, that `Page` is very generic, so we can use that for scaffolding the others `Pages`.

Now, if we need to edit our data structure, we won't need to do multiple edits in 2 files. We just need to edit the `Class Model`.

In the next article, we'll talk about a very special case, `collections`, a.k.a `arrays`.