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.