Vue.js 3 Pinia store is only partially reactive. Why?

Viewed 49

I'm using Pinia as Store for my Vue 3 application. The problem is that the store reacts on some changes, but ignores others.

The store looks like that:

state: () => {
  return {
    roles: [],
    currentRole: 'Administrator',
    elements: []
  }
},
getters: {
  getElementsForCurrentRole: (state) => {
    let role = state.roles.find((role) => role.label == state.currentRole);
    if (role) {
      return role.permissions.elements;
    }
  }
},

In the template file, I communicate with the store like this:

<template>
  <div>
    <draggable 
      v-model="getElementsForCurrentRole" 
      group="elements" 
      @end="onDragEnd" 
      item-key="name">
      <template #item="{element}">
        <n-card :title="formatElementName(element.name)" size="small" header-style="{titleFontSizeSmall: 8px}" hoverable>
          <n-switch v-model:value="element.active" size="small" />
        </n-card>
      </template>
    </draggable>
  </div>
</template>

<script setup>
  import { NCard, NSwitch } from 'naive-ui';
  import draggable from 'vuedraggable'
  import { usePermissionsStore } from '@/stores/permissions';
  import { storeToRefs } from 'pinia';

  const props = defineProps({
    selectedRole: {
      type: String
    }
  })

  const permissionsStore = usePermissionsStore();
  const { getElementsForCurrentRole, roles } = storeToRefs(permissionsStore);

  const onDragEnd = () => {
    permissionsStore.save();
  }
  
  const formatElementName = (element) => {
    let title = element.charAt(0).toUpperCase() + element.slice(1);
    title = title.replace('-', ' ');
    title = title.split(' ');
    if (title[1]) {
      title = title[0] + ' ' + title[1].charAt(0).toUpperCase() + title[1].slice(1);
    }
    if (typeof title == 'object') {
      return title[0];
    }

    return title;
  }
  
</script>

My problem is the v-model="getElementsForCurrentRole". When making changes, for example changing the value for the switch, the store is reactive and the changes are made successfully. But: If I try to change the Array order by dragging, the store does not update the order. I'm confused, because the store reacts on other value changes, but not on the order change.

What can be the issue here? Do I something wrong?

-Edit- I see the following warning on drag: Write operation failed: computed value is readonly

Workaround

As workaround I work with the drag event and write the new index directly to the store variable. But...its just a workaround. I would really appreciate a cleaner solution.

Here is the workaround code:

onDrag = (event) => {
  if (event && event.type == 'end') {
    // Is Drag Event. Save the new order manually directly in the store
    let current = permissionsStore.roles.find((role) => role.value == permissionsStore.currentRole);
    
    var element = current.permissions.elements[event.oldIndex];
    current.permissions.elements.splice(event.oldIndex, 1);
    current.permissions.elements.splice(event.newIndex, 0, element);
  }
}
1 Answers

You should put reactive value on v-model.

getElementsForCurrentRole is from getters, so it is treated as computed value.

Similar to toRefs() but specifically designed for Pinia stores so methods and non reactive properties are completely ignored.

https://pinia.vuejs.org/api/modules/pinia.html#storetorefs

I think this should work for you.

// template
v-model="elementsForCurrentRole"  

// script
const { getElementsForCurrentRole, roles } = storeToRefs(permissionsStore); 
const elementsForCurrentRole = ref(getElementsForCurrentRole.value);
Related