"No storage option exists to persist the session" error when using Supabase and Zustand in a Next.js application with Clerk.dev authentication?

huangapple go评论59阅读模式
英文:

"No storage option exists to persist the session" error when using Supabase and Zustand in a Next.js application with Clerk.dev authentication?

问题

I have set up a Next.js application with authentication handled by Clerk.dev and data storage managed by Supabase. I am also using Zustand for state management. However, I am encountering an error that says "No storage option exists to persist the session, which may result in unexpected behavior when using auth."

Here is a simplified version of my code:

File: utils\supabase.ts

import { createClient, SupabaseClient } from '@supabase/supabase-js';

const supabaseUrl =  process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_KEY;

export const supabase: SupabaseClient = createClient(supabaseUrl, supabaseKey);

File: store\useUserStore.ts

import { create } from 'zustand';
import { supabase } from '../utils/supabase';

type User = {
    id: string;
    email: string;
    custom_api: string;
    bot_type: string;
    discord_id: string;
};

type UserStore = {
    userData: User | null;
    status: 'idle' | 'loading' | 'error';
    error: string | null;
    fetchUser: (userId: string) => Promise<void>;
    createUser: (userId: string, user: Partial<User>) => Promise<void>;
    updateUser: (userId: string, updates: Partial<User>) => Promise<void>;
    deleteUser: (userId: string) => Promise<void>;
};

export const useUserStore = create<UserStore>((set) => ({
    userData: null,
    status: 'idle',
    error: null,
    fetchUser: async (userId) => {
        try {
            set({ status: 'loading' });
            const { data, error } = await supabase
            .from('users')
            .select()
            .eq('id', userId)
            if (error) {
                throw new Error('Error fetching user');
            }
            set({ userData: data![0] ?? null, status: 'idle', error: null });
        } catch (error) {
            set({ status: 'error', error: error.message });
        }
    },
    createUser: async (userId, user) => {
       // code here
    },
    updateUser: async (userId, updates) => {
        // code here
    },
    deleteUser: async (userId) => {
        // code here
    },
}));

The error message states that no storage option exists to persist the session, which could lead to unexpected behavior when using authentication. I want to enable the persistSession option but I'm unsure how to proceed.

For additional context, I am using Clerk.dev for authentication, connecting it to Supabase using JWT. I have also set up a Row-Level Security (RLS) policy for the SELECT operation, targeting all (public) roles and using a WITH CHECK expression.

I would greatly appreciate any guidance or suggestions on how to resolve this error and properly configure the session storage to work seamlessly with Clerk.dev, Supabase, and Zustand. Thank you!

Back then, my code was structured like this:

import { createClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/nextjs'
/// .....
const supabaseClient = async supabaseAccessToken => {
    const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_KEY,
        {
            global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
        }
    )
    return supabase
}
/// .....
export default function Messages() {
//...
const { session } = useSession()
//...
const supabaseAccessToken = await session.getToken({
    template: 'Supabase'
})
const supabase = await supabaseClient(supabaseAccessToken)
const { data } = await supabase
    .from('user')
    .insert(
        {
            id: session.user.id,
            email: session.user?.primaryEmailAddress?.emailAddress,
        }
    )
    .select()
setUserSettings(data[0])
//...

This code looks messy because I'm doing it directly in a .tsx file. I want to use state management (Zustand) to improve the code structure and adhere to good practices.

英文:

I have set up a Next.js application with authentication handled by Clerk.dev and data storage managed by Supabase. I am also using Zustand for state management. However, I am encountering an error that says "No storage option exists to persist the session, which may result in unexpected behavior when using auth."

    &quot;@clerk/nextjs&quot;: &quot;^4.16.4&quot;,
&quot;next&quot;: &quot;^12.0.9&quot;,
&quot;react&quot;: &quot;17.0.2&quot;,
&quot;zustand&quot;: &quot;^4.3.8&quot;

Here is a simplified version of my code:

File: utils\supabase.ts

import { createClient, SupabaseClient } from &#39;@supabase/supabase-js&#39;;

const supabaseUrl =  process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_KEY;

export const supabase: SupabaseClient = createClient(supabaseUrl, supabaseKey);

File: store\useUserStore.ts

import { create } from &#39;zustand&#39;;
import { supabase } from &#39;../utils/supabase&#39;;

type User = {
    id: string;
    email: string;
    custom_api: string;
    bot_type: string;
    discord_id: string;
};

type UserStore = {
    userData: User | null;
    status: &#39;idle&#39; | &#39;loading&#39; | &#39;error&#39;;
    error: string | null;
    fetchUser: (userId: string) =&gt; Promise&lt;void&gt;;
    createUser: (userId: string, user: Partial&lt;User&gt;) =&gt; Promise&lt;void&gt;;
    updateUser: (userId: string, updates: Partial&lt;User&gt;) =&gt; Promise&lt;void&gt;;
    deleteUser: (userId: string) =&gt; Promise&lt;void&gt;;
};

export const useUserStore = create&lt;UserStore&gt;((set) =&gt; ({
    userData: null,
    status: &#39;idle&#39;,
    error: null,
    fetchUser: async (userId) =&gt; {
        try {
            set({ status: &#39;loading&#39; });
            const { data, error } = await supabase
            .from(&#39;users&#39;)
            .select()
            .eq(&#39;id&#39;, userId)
            if (error) {
                throw new Error(&#39;Error fetching user&#39;);
            }
            set({ userData: data![0] ?? null, status: &#39;idle&#39;, error: null });
        } catch (error) {
            set({ status: &#39;error&#39;, error: error.message });
        }
    },
    createUser: async (userId, user) =&gt; {
       // code here
    },
    updateUser: async (userId, updates) =&gt; {
        // code here
    },
    deleteUser: async (userId) =&gt; {
        // code here
    },
}));

The error message states that no storage option exists to persist the session, which could lead to unexpected behavior when using authentication. I want to enable the persistSession option but I'm unsure how to proceed.

For additional context, I am using Clerk.dev for authentication, connecting it to Supabase using JWT. I have also set up a Row-Level Security (RLS) policy for the SELECT operation, targeting all (public) roles and using a WITH CHECK expression.

I would greatly appreciate any guidance or suggestions on how to resolve this error and properly configure the session storage to work seamlessly with Clerk.dev, Supabase, and Zustand. Thank you!

Back then, my code was structured like this:

import { createClient } from &#39;@supabase/supabase-js&#39;
import { useSession } from &#39;@clerk/nextjs&#39;
/// .....
const supabaseClient = async supabaseAccessToken =&gt; {
    const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_KEY,
        {
            global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
        }
    )
    return supabase
}
/// .....
export default function Messages() {
//...
const { session } = useSession()
//...
const supabaseAccessToken = await session.getToken({
    template: &#39;Supabase&#39;
})
const supabase = await supabaseClient(supabaseAccessToken)
const { data } = await supabase
    .from(&#39;user&#39;)
    .insert(
        {
            id: session.user.id,
            email: session.user?.primaryEmailAddress?.emailAddress,
        }
    )
    .select()
setUserSettings(data[0])
//...

This code looks messy because I'm doing it directly in a .tsx file. I want to use state management (Zustand) to improve the code structure and adhere to good practices.

答案1

得分: 0

我能够在不使用本地存储的情况下实现这一点,正如Discord社区中其他人所推荐的。

最初,我不得不寻找替代方案。所以,我将会话管理整合到状态管理中。在客户端组件中,这是我的代码:

import {
  useSession,
  //....
} from '@clerk/nextjs';
import React, { useState, useEffect } from 'react';

interface ContentsProps {
  //...
}

const Contents = (props: ContentsProps) => {

  const { session } = useSession()
  const { userData, status, error, fetchUser } = useUserStore();
  //...
  useEffect(() => {
    if (session) {
      fetchUser(session.user.id, session);
    }
  }, []);

  return (
  //...
  );
};

export default Contents;

结果,我不再需要./utils/supabase.ts文件。我直接插入或包含它在存储中。因此,这是我的更新后的./store/useUserStore.ts文件:

import { create } from 'zustand';
import { createClient } from '@supabase/supabase-js';

const supabaseClient = async supabaseAccessToken => {
    const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_KEY,
        {
            global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
        }
    )
    return supabase
}

type User = {
    id: string;
    email: string;
    custom_api: string;
    bot_type: string;
    discord_id: string;
};

type UserStore = {
    userData: User | null;
    status: 'idle' | 'loading' | 'error';
    error: string | null;
    fetchUser: (userId: string, session: any) => Promise<void>;
    //....
};

export const useUserStore = create<UserStore>((set) => ({
    userData: null,
    status: 'idle',
    error: null,
    fetchUser: async (userId, session) => {
        try {
        set({ status: 'loading' });
        const supabaseAccessToken = await session.getToken({
            template: 'Supabase'
        })
        const supabase = await supabaseClient(supabaseAccessToken)
        const { data, error } = await supabase
            .from('users')
            .select()
            .eq('id', userId)
            console.log(data)
        if (error) {
            console.error(error)
            throw new Error('Error fetching user');
        }
        set({ userData: data![0] ?? null, status: 'idle', error: null });
        } catch (error) {
        set({ status: 'error', error: error.message });
        }
    },
    //....
}));

我希望这个增强的解释清楚了我所做的更改,并提供了对代码的更清晰的理解。

英文:

I was able to achieve this without using local storage, as recommended by other people in the Discord community.

Initially, I had to find an alternative solution. So, what I did was incorporate session management into state management. In the client-side component, here's my code:

import {
  useSession,
  //....
} from &#39;@clerk/nextjs&#39;
import React, { useState, useEffect } from &#39;react&#39;;

interface ContentsProps {
  //...
}

const Contents = (props: ContentsProps) =&gt; {

  const { session } = useSession()
  const { userData, status, error, fetchUser } = useUserStore();
  //...
  useEffect(() =&gt; {
    if (session) {
      fetchUser(session.user.id, session);
    }
  }, []);

  return (
  //...
  );
};

export default Contents;

As a result, I no longer have the ./utils/supabase.ts file. I directly inserted or included it in the store. Therefore, this is my updated ./store/useUserStore.ts file:

import { create } from &#39;zustand&#39;;
import { createClient } from &#39;@supabase/supabase-js&#39;

const supabaseClient = async supabaseAccessToken =&gt; {
    const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_KEY,
        {
            global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
        }
    )
    return supabase
}

type User = {
    id: string;
    email: string;
    custom_api: string;
    bot_type: string;
    discord_id: string;
};

type UserStore = {
    userData: User | null;
    status: &#39;idle&#39; | &#39;loading&#39; | &#39;error&#39;;
    error: string | null;
    fetchUser: (userId: string, session: any) =&gt; Promise&lt;void&gt;;
    //....
};

export const useUserStore = create&lt;UserStore&gt;((set) =&gt; ({
    userData: null,
    status: &#39;idle&#39;,
    error: null,
    fetchUser: async (userId, session) =&gt; {
        try {
        set({ status: &#39;loading&#39; });
        const supabaseAccessToken = await session.getToken({
            template: &#39;Supabase&#39;
        })
        const supabase = await supabaseClient(supabaseAccessToken)
        const { data, error } = await supabase
            .from(&#39;users&#39;)
            .select()
            .eq(&#39;id&#39;, userId)
            console.log(data)
        if (error) {
            console.error(error)
            throw new Error(&#39;Error fetching user&#39;);
        }
        set({ userData: data![0] ?? null, status: &#39;idle&#39;, error: null });
        } catch (error) {
        set({ status: &#39;error&#39;, error: error.message });
        }
    },
    //....
}));

I hope this enhanced explanation clarifies the changes I made and provides a clearer understanding of the code."

huangapple
  • 本文由 发表于 2023年6月29日 21:24:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76581496.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定