[v6][Vue] Reactivity and the new Store

I have some issues when trying to understand how reactivity is supposed to work, if we use the new store concept.

E.g. I just have an array of products, like so:

products = [{
   title: 'Product 1',
   id: 1
}, {
   title: 'Product 2',
   id:2
}

]

When running the app, everything is fine at first (after finishing the request, the list shows up just fine), but then:

  • I want a list to get updated, when a product changes (or the list ist sorted).
  • I also want to ‘pin’ the first product to the top (e.g. after sorting, the ‘pinnedProduct’ is #2, else #1)

The problem is, that the list is only updated, if I use a computed property in the SFC

here’s what I have in the store (more or less taken from the examples available):

import {createStore, request} from 'framework7/lite';

const store = createStore({
    state: {
        products: [],
    },
    getters: {
        // I want to keep a product reactively pinned to top
        pinnedProduct({state}) {
            return state.products[0];
        },
        products({state}) {
            return state.products;
        },
    },
    actions: {
        getProducts({state}, url) {
            request.get(url).then((res) => {
                state.products = JSON.parse(res.data);
            });
        },
        sortProducts({state}, direction = false) {
            state.products = direction ? [...state.products.sort((a, b,) => a.id < b.id)] : [...state.products.sort((a, b,) => a.id > b.id)];
        },
    },
});
export default store;

in a Vue SFC:

<template>
    <!-- accessing property: doesn't redraw when value changes -->
    <f7-list v-if="pinned.value">
       <f7-list-item>{{ pinned.value.title }}</f7-list-item>
    </f7-list>

    <!-- accessing store.getters directly: doesn't redraw when value changes -->
    <f7-list v-if="store.getters.pinnedProduct.value">
       <f7-list-item>{{ store.getters.pinnedProduct.value.title }}</f7-list-item>
    </f7-list>

    <!-- OK!! computed property redraws when value changes -->
    <f7-list v-if="pinnedProduct.length">
        <f7-list-item v-for="(value, key) in pinnedProduct"> {{ value.title}} </f7-list-item>
    </f7-list> -->
</template>
<script>
import { onMounted } from 'vue';
import { useStore } from 'framework7-vue';
import store from '../js/store';

export default {
        setup() {
            const products = useStore('products');
            onMounted(() => {
                store.dispatch('getProducts');
            });
            return {
                store,
                products,
                pinned: store.getters.pinnedProduct
            };
        },
        computed: {
            pinnedProduct() {
                return store.getters.pinnedProduct.value;
            }
        },
    };
</script>

How is reactivity supposed to work here?
Any insights?

const products = useStore('products');
const pinner = useStore('pinnedProduct');
onMounted(() => {
    store.dispatch('getProducts');
});
return {
    store,
    products,
    pinned,
};

Ok thanks… I thought I tried that too, but that seems to work, indeed.

That also means, I have to wrap dynamically created items/properties into a dedicated object:

 <template>
    <f7-block-title>{{ test.a }}</f7-block-title>
</template>

const products = useStore('test');
return {
    store,
    test,
};

...
mounted() {
      setTimeout(() => {
          this.store.state.test.a = 55;  / / doesn't work/update the DOM
          // this.test.a = 66; // **works - DOM is updated (very insteresting)**
          this.store.dispatch('setTest', 99); // works fine
      }, 1000);
  },

Store:

const store = createStore({
    state: {
        test: {
          a: 1,
          b: 2
      }
    },
    getters: {
        test({state}) {
            return state.test;
        }
    },
    setTest({state}, num) {
         state.test = Object.assign({}, state.test, {a: num});
    },
});

I hope, I got this right?!

Anyway, to me, this feels a little like a step back in convenience, but maybe I just must get used to it…

You should modify the state ONLY within store actions.

This:

this.store.state.test.a = 55;

not suppose to work

This:

this.test.a = 66;

works because local component value was wrapped with Vue’s ref, but you shouldn’t rely on this, this is more an exception rather than rule and more likely it modified just a local value but not in the store

If you work with Vue, you can try their native thing Vuex for store management https://vuex.vuejs.org, but F7’s store very similar to Vuex but without mutation

thx for the info.

Btw:

this.test.a = 66;

updates data on the store AND UI fine, while

this.store.state.test.a = 55;

just updates the store values…

Anyway - I will play a little with it, but it seems it’s just a bit too far away from my workflow.

I’m operating mostly on (dynamic) data, so store-actions is only a fallback for known properties. (That’s why I don’t like Vuex, because for me it adds to much boilerplate to my code and makes it harder to update UI and re-use code).

I’m using the observable (bus) pattern since forever and I’m very happy with it! (It also never failed for me)

export const store = Vue.observable({
    state: {
        products: []
    },
    setProp(prop, value) {
        // we can add some code to validate/verify (or even put it after a Promise)
        // and then simply update - or not
        this.state[prop] = value;
    },
})

I know, data-mutation is the unloved brother of today’s web-devs, but :man_shrugging:

Just a note to myself (and anybody who is interested tinkering with data). Switching from Vue v2’s Vue.observable to v3 is strightforward:

export const store = reactive({
    state: {
        products: []
     },

    setProp(prop, value) {
        this.state[prop] = value;
    },
});

of course we can just make the state reactive:

export const store = {
    state: reactive({
        products: []
     }),

    setProp(prop, value) {
        this.state[prop] = value;
    },
}

Disclaimer: This seems NOT to be a common practice. To safe-guard against all kind of troubles, use @nolimits4web 's new Store in v6, which is very cool and works fine as well.