nuxt-modules / supabase

Supabase module for Nuxt.
https://supabase.nuxtjs.org
MIT License
733 stars 129 forks source link

Using useSupabaseClient inside a composable then used in plugin is breaking #316

Closed matt-clegg closed 10 months ago

matt-clegg commented 11 months ago

I'm trying to load some user roles and permission data when the Nuxt page first loads. I'm doing this in a roles.ts plugin.

import { useUserRoles } from "~/composables/resources/useUserRoles";
import { useSupabaseUser } from "#imports";
import type { Tables } from "~/types/supabase";

export default defineNuxtPlugin(async () => {
  const user = useSupabaseUser();
  const { load, loadRolePermissions } = useUserRoles();

  const rolesState = useState<Tables<"user_roles"> | null>("user-role", () => null);
  const rolePermissionsState = useState<Tables<"role_permissions">[] | null>("user-role-permissions", () => null);

  async function loadRoleData () {
    if (user.value) {
      const role = await load();

      if (role.value) {
        if (role.value.error) {
          if (role.value.error.code === "PGRST116") {
            throw createError({
              message: "User does not have a role defined",
              statusMessage: "Missing user role"
            });
          } else {
            throw createError({
              message: "Unable to load role for user: " + role.value.error.message
            });
          }
        }

        if (role.value.data) {
          rolesState.value = role.value.data;
        } else {
          console.warn("No roles found");
        }
      } else {
        console.warn("Could not load roles for user: " + user.value.id);
      }
    }
  }

  async function loadPermissions (role: string) {
    const permissions = await loadRolePermissions(role);

    if (permissions.value) {
      if (permissions.value.error) {
        if (permissions.value.error.code !== "PGRST116") {
          throw createError({
            message: "Unable to load permissions for role: " + permissions.value.error.message,
            statusMessage: "Missing permissions"
          });
        } else {
          console.warn("Role does not have permissions defined");
        }

        return null;
      }

      rolePermissionsState.value = permissions.value.data;
    } else {
      console.warn("Could not load permissions for role: " + role);
    }
  }

  async function loadData () {
    await loadRoleData();
    if (rolesState.value?.role) {
      await loadPermissions(rolesState.value.role);
    } else {
      console.warn("Could not load permissions without a role");
    }
  }

  watch(user, async (val) => {
    if (val) {
      await loadData();
    } else {
      rolesState.value = null;
      rolePermissionsState.value = null;
    }
  });

  await loadData();
});

This plugin has been using the following composable:

import type { Database } from "~/types/supabase";

export function useUserRoles () {
  const client = useSupabaseClient<Database>();
  const user = useSupabaseUser();

  async function load () {
    const { data } = await useAsyncData("user-role", async () => {
      if (user.value) {
        const { data, error } = await client
          .from("user_roles")
          .select("*")
          .eq("user_id", user.value.id)
          .single();

        return { data, error };
      }

      return null;
    });

    return data;
  }

  async function loadRolePermissions (role: string) {
    const { data } = await useAsyncData("user-role-permissions", async () => {
      const { data, error } = await client
        .from("role_permissions")
        .select("*")
        .eq("role", role);

      return { data, error };
    });

    return data;
  }

  return {
    load,
    loadRolePermissions
  };
}

I originally started by just making a call to load data from the user-role table. This was working fine. I would refresh the page and the data would be loaded on the server and included in the __NUXT_DATA__ payload.

However, when I added a second call to load permission data from a different table, the page would return a 500 error A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function.

I'm a bit confused as to why the plugin breaks when I include the second load call. The code above will work if you comment out the await loadPermissions(rolesState.value.role); line in the loadData function of the plugin.

atinux commented 10 months ago

Hi @matt-clegg

Try to not use useAsyncData in your plugins, this composable should be used only inside Pages or components.

As you are using useState, you can leverage this to know if the store has been filed during SSR for hydration.

matt-clegg commented 10 months ago

Thanks for the reply @Atinux, useAsyncData was the culprit, thanks!