Home > Net >  Can't call a function on an object converted to ref and retrieved from pinia store
Can't call a function on an object converted to ref and retrieved from pinia store

Time:08-15

I have a Vue3 app with TypeScript and Pinia.

In the _Layout.vue component when I call currentUser.value.hasPermission() I get the error Uncaught (in promise) TypeError: currentUser.value.hasPermission is not a function.

Is there something with refs or pinia stores that would prevent me from calling a function on an object that had been stored in them?

Code below.

// @/stores/main-store.ts

import { defineStore } from "pinia";
import type { IUser } from "@/lib/user";

export const useStore = defineStore({
  id: "main",
  state: () => ({
    currentUser: {} as IUser,
    currentTenant: null as string | null,
    theme: null as string | null,
  }),
});
// @/lib/user.ts

import { getToken } from "@/lib/auth";

export interface IUser {
  id: string;
  tenants: IUserTenant[];
  hasPermission: (
    tenantIdentifier: string|null,
    permission: IUserPermission
  ) => boolean;
}

export interface IUserTenant {
  tenantIdentifier: string;
  tenantName: string;
  permissions: IUserPermission[];
}

export interface IUserPermission {
  permissionZone: PermissionZone;
  permissionType: PermissionType;
}

export enum PermissionZone {
  Me,
  Tenants,
  Users,
  Contacts,
}
export enum PermissionType {
  List,
  Read,
  Create,
  Update,
  Delete,
}

const API_EP = import.meta.env.VITE_API_ENDPOINT;
export class User implements IUser {

  public static async getCurrentUser(): Promise<IUser> {
    const token = await getToken();
    const response = await fetch(`${API_EP}/user`, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
      mode: "cors",
    });

    if (response.ok) {
      return (await response.json()) as User;
    }

    // TODO Define Error response model and parse for message
    throw new Error("Unable to retrieve current user.");
  }

  public id!: string;
  public tenants: IUserTenant[] = [];
  public hasPermission(tenantIdentifier: string | null, permission: IUserPermission): boolean {
    return this.tenants.some(
      (t) =>
        t.tenantIdentifier === tenantIdentifier &&
        t.permissions.some(
          (p) =>
            p.permissionZone === permission.permissionZone &&
            p.permissionType === permission.permissionType
        )
    );
  }
}
// @/views/_Layout.vue - <script> section

import { onMounted, ref } from "vue";
import { useStore } from "@/stores/main-store";
import { storeToRefs } from "pinia";
import { Navigation } from "@/lib/navigation";
import type { INavigationItem } from "@/lib/navigation";

const store = useStore();
const { currentUser, currentTenant } = storeToRefs(store);

const navItems = ref<INavigationItem[]>();
onMounted(async () => {
  navItems.value = Navigation.filter((i) =>
    currentUser.value.hasPermission(
      currentTenant.value,
      i.permission
    )
  );
});

Edit I forgot about where the currentUser gets populated - this is done in the router setup as follows:

// @/router/index.ts

import { createRouter, createWebHistory } from "vue-router";
import Layout from "@/views/_Layout.vue";
import { AuthenticationGuard } from "vue-auth0-plugin";
import { User } from "@/lib/user";
import { useStore } from "@/stores/main-store";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      name: "default",
      path: "/",
      component: Layout,
      beforeEnter: async (to, from) => {
        // Handle login
        const authed = await AuthenticationGuard(to, from);

        useStore().$patch({
          currentUser: await User.getCurrentUser(),
        });

        if (authed && to.name === "default")
          await router.push({ name: "dashboard" });
        return authed;
      },
      children: [
        {
          name: "dashboard",
          path: "",
          component: () => import("@/views/DashboardPage.vue"),
        },
      ],
    },
  ],
});

export default router;

CodePudding user response:

currentUser is populated from getCurrentUser(), which returns the JSON object from a fetch():

// @/lib/user.ts
public static async getCurrentUser(): Promise<IUser> {
  ⋮
  if (response.ok) {
    return (await response.json()) as User;
  }
}

fetch() returns serializable data objects, which cannot contain functions, so I don't believe it's correct to cast the JSON response as IUser.

If I understand the problem correctly, getCurrentUser() should actually create an instance of User from the JSON response, and return that instance. You could do that by creating a class constructor that receives the JSON response, and copies over the data into its fields.

  • Related