Home > Enterprise >  Why is this composable not reactive?
Why is this composable not reactive?

Time:06-30

I have this composable in my app:

// src/composition-api/usePermissions.js
import { ref, readonly } from 'vue'
import { fetchData } from 'src/utils/functions/APIFunctions'

export function usePermissions() {
  const permissions = ref([])
  const name = ref('')

  const fetchCurrentUser = () => {
    fetchData('users/me').then(res => {
      name.value = `${res.first_name} ${res.last_name}`
      permissions.value = res.roles
    })
  }

  return {
    name: readonly(name),
    permissions: readonly(permissions),
    fetchCurrentUser,
  }
}

FetchCurrentUser is called in the main layout component.

<template>
  <!-- src/layouts/MainLayout.vue -->
  <q-layout view="lHh Lpr lFf">
    <!-- Redacted for brevity -->
  </q-layout>
</template>

<script setup>
import { defineComponent, ref, onMounted } from "vue";
import EssentialLink from "src/components/EssentialLink.vue";
import { usePermissions } from "src/composition-api/usePermissions";

const { fetchCurrentUser } = usePermissions();

/* redacted for brevity */

onMounted(() => {
  fetchCurrentUser();
});
</script>

And the state is used in other components, such as this one.

<template>
 <!-- src/components/loggedUserLabel.vue -->
  <div>{{ name }}</div>
</template>
<script setup>
import { usePermissions } from "src/composition-api/usePermissions.js";

const { name } = usePermissions();
</script>

The fetchCurrentUser() function is used when starting the app or when a new user logs in. I want to use this composable in other components to restrict access to some parts of the app depending on user permissions, and to display the username. However, the name and permissions properties are not reacting to changes. What could be wrong here?

If it matters, I'm using Quasar. I could use Pinia for this but I only need this kind of store-like shared state here, so it seems like overkill to add another library.

At Estus Flask's request, I have created a MCVE - and the same problem keeps happening. Note how in src/layouts/MainLayout.vue the API call is made, but the message in src/components/PokemonLabel.vue does not update, despite receiving a valid response from the Pokeapi.

I have found a similar answered question but it is about a different situation.

Thanks in advance for your help!

CodePudding user response:

name and fetchCurrentUser are supposed to be used in the same component. The state of fetchCurrentUser isn't shared between components, this is by design.

In order to make the state global, it should be created once per component hierarchy, e.g.:

const name = ref('')

export function usePermissions() {
  const fetchCurrentUser = ...

  return {
    name: readonly(name),
    fetchCurrentUser,
  }
}

This won't work correctly for SSR application like Quasar because there are multiple application instances for different users, but the state is created once and shared between them. In this case the state likely should be created for a hierarchy of components, e.g.:

export function setupPermissions() {
  const name = ref('')
  const fetchCurrentUser = ...


  provide('permissionsStore', {
    name: readonly(name),
    fetchCurrentUser,
  })
}

export function usePermissions() {
  return inject('permissionsStore');
}

Where usePermissions is used in child component. And setupPermissions is used in root component, or can be rewritten as a plugin.

  • Related