Skip to content

Commit 67df23d

Browse files
committed
fix(PayloadSessionProvider): Fix broken PayloadSessionProvider
- Add loading state - Add refresh function - Move PayloadSessionProvider export to client scope - Remove PayloadSessionProviderWrapper
1 parent 382bac5 commit 67df23d

File tree

8 files changed

+74
-49
lines changed

8 files changed

+74
-49
lines changed

packages/dev/src/app/(app)/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PayloadSessionProvider } from "payload-authjs";
1+
import { getPayloadSession } from "payload-authjs";
2+
import { PayloadSessionProvider } from "payload-authjs/client";
23
import { AuthjsProviders } from "../components/auth/authjs/AuthjsProviders";
34
import { AuthjsSessionClient } from "../components/auth/authjs/AuthjsSessionClient";
45
import { AuthjsSessionServer } from "../components/auth/authjs/AuthjsSessionServer";
@@ -10,7 +11,7 @@ import { SignInOrOutButtons } from "../components/auth/SignInOrOutButtons";
1011
import ExampleList from "../components/ExampleList";
1112
import { Tabs } from "../components/general/Tabs";
1213

13-
const Page = () => {
14+
const Page = async () => {
1415
return (
1516
<main className="container mt-5">
1617
<SignInOrOutButtons />
@@ -24,7 +25,7 @@ const Page = () => {
2425
{
2526
label: "Payload [usePayloadSession] (client)",
2627
content: (
27-
<PayloadSessionProvider>
28+
<PayloadSessionProvider session={await getPayloadSession()}>
2829
<PayloadSessionClientWithUsePayloadSession />
2930
</PayloadSessionProvider>
3031
),

packages/dev/src/app/components/auth/payload/PayloadSessionClientWithUsePayloadSession.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { usePayloadSession } from "payload-authjs/client";
44
import Badge from "../../general/Badge";
55

66
export const PayloadSessionClientWithUsePayloadSession = () => {
7-
const { session, refresh } = usePayloadSession();
7+
const { status, session, refresh } = usePayloadSession();
88

99
return (
1010
<>
1111
<div className="mb-2 flex flex-col items-start gap-2">
12-
<Badge variant={session?.user ? "green" : "red"}>
13-
status: {session?.user ? "authenticated" : "unauthenticated"}
12+
<Badge
13+
variant={status === "authenticated" ? "green" : status === "loading" ? "yellow" : "red"}
14+
>
15+
status: {status}
1416
</Badge>
1517
{session?.expires && (
1618
<Badge onClick={refresh}>Expires: {new Date(session.expires).toLocaleString()}</Badge>

packages/payload-authjs/README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,20 @@ const ServerComponentExample = async () => {
110110

111111
Instead of using the [`useSession`](https://authjs.dev/getting-started/session-management/get-session?framework=next-js-client) hook of Auth.js, you can use the `usePayloadSession` hook to get the current session in the client-side code:
112112

113-
Before you can use the `usePayloadSession` hook, you need to wrap your app with the `PayloadSessionProvider` on the server-side:
113+
Before you can use the `usePayloadSession` hook, you need to wrap your app with the `PayloadSessionProvider`:
114114

115115
```tsx
116116
// layout.tsx
117-
import { PayloadSessionProvider } from "payload-authjs";
117+
import { PayloadSessionProvider } from "payload-authjs/client";
118+
import { getPayloadSession } from "payload-authjs";
118119

119-
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
120+
const Layout: React.FC<{ children: React.ReactNode }> = async ({ children }) => {
120121
return (
121122
<html lang="en">
122123
<body>
123-
<PayloadSessionProvider>{children}</PayloadSessionProvider>
124+
<PayloadSessionProvider session={await getPayloadSession()}>
125+
{children}
126+
</PayloadSessionProvider>
124127
</body>
125128
</html>
126129
);
@@ -129,6 +132,8 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
129132
export default Layout;
130133
```
131134

135+
> ℹ️ Passing the session to the `PayloadSessionProvider` is optional, but it can be useful to avoid loading states.
136+
132137
You are now ready to use the `usePayloadSession` hook in your client-side code:
133138

134139
```tsx

packages/payload-authjs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"payloadcms",
4141
"plugin",
4242
"typescript",
43+
"nextjs",
4344
"react",
4445
"auth.js",
4546
"next-auth"

packages/payload-authjs/src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export { PayloadSessionProvider } from "./payload/session/PayloadSessionProvider";
12
export { usePayloadSession } from "./payload/session/usePayloadSession";

packages/payload-authjs/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@ export { withPayload } from "./authjs/withPayload";
44
export { getPayloadUser } from "./payload/getPayloadUser";
55
export { authjsPlugin, type AuthjsPluginConfig } from "./payload/plugin";
66
export { getPayloadSession, type PayloadSession } from "./payload/session/getPayloadSession";
7-
export { PayloadSessionProviderWrapper as PayloadSessionProvider } from "./payload/session/PayloadSessionProviderWrapper";

packages/payload-authjs/src/payload/session/PayloadSessionProvider.tsx

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
"use client";
22

33
import type { CollectionSlug, DataFromCollectionSlug } from "payload";
4-
import { createContext, type ReactNode, useState } from "react";
4+
import { createContext, type ReactNode, useCallback, useEffect, useState } from "react";
55
import type { PayloadSession } from "./getPayloadSession";
66

77
export interface SessionContext<TSlug extends CollectionSlug> {
8+
/**
9+
* The status of the session
10+
*/
11+
status: "loading" | "authenticated" | "unauthenticated";
812
/**
913
* The session
1014
*/
@@ -13,11 +17,17 @@ export interface SessionContext<TSlug extends CollectionSlug> {
1317
* Function to refresh the session
1418
*/
1519
refresh: () => Promise<PayloadSession<TSlug> | null>;
20+
/**
21+
* Function to refetch the session from the server
22+
*/
23+
refetch: () => Promise<PayloadSession<TSlug> | null>;
1624
}
1725

1826
export const Context = createContext<SessionContext<never>>({
27+
status: "loading",
1928
session: null,
2029
refresh: () => new Promise(resolve => resolve(null)),
30+
refetch: () => new Promise(resolve => resolve(null)),
2131
});
2232

2333
interface Props<TSlug extends CollectionSlug> {
@@ -28,25 +38,62 @@ interface Props<TSlug extends CollectionSlug> {
2838
*/
2939
userCollectionSlug?: TSlug;
3040
/**
31-
* The session (if available)
41+
* The session from the server
42+
*
43+
* @default null
3244
*/
33-
session: PayloadSession<TSlug> | null;
45+
session?: PayloadSession<TSlug> | null;
3446
/**
3547
* The children to render
3648
*/
3749
children: ReactNode;
3850
}
3951

4052
/**
41-
* PayloadSessionProvider (client-side) that provides the session to the context provider
53+
* PayloadSessionProvider that provides the session to the context provider
4254
*/
4355
export const PayloadSessionProvider = <TSlug extends CollectionSlug = "users">({
4456
userCollectionSlug = "users" as TSlug,
45-
session,
57+
session = null,
4658
children,
4759
}: Props<TSlug>) => {
60+
const [isLoading, setIsLoading] = useState(!session);
4861
const [localSession, setLocalSession] = useState<PayloadSession<TSlug> | null>(session);
4962

63+
/**
64+
* Function to fetch the session
65+
*/
66+
const fetchSession = useCallback(async () => {
67+
// Fetch the session from the server
68+
const response = await fetch(`/api/${userCollectionSlug}/me`);
69+
const result: { user: DataFromCollectionSlug<TSlug>; exp: number } = await response.json();
70+
71+
// Set loading to false
72+
setIsLoading(false);
73+
74+
// If the response is not ok or the user is not present, return null
75+
if (!response.ok || !result.user) {
76+
return null;
77+
}
78+
79+
// Update the local session
80+
const localSession = {
81+
user: result.user,
82+
expires: new Date(result.exp * 1000).toISOString(),
83+
};
84+
setLocalSession(localSession);
85+
86+
// Return the session
87+
return localSession;
88+
}, [userCollectionSlug]);
89+
90+
/**
91+
* On mount, fetch the session
92+
*/
93+
useEffect(() => {
94+
void fetchSession();
95+
}, [fetchSession]);
96+
5097
/**
5198
* Function to refresh the session
5299
*/
@@ -76,8 +123,10 @@ export const PayloadSessionProvider = <TSlug extends CollectionSlug = "users">({
76123
return (
77124
<Context
78125
value={{
126+
status: isLoading ? "loading" : localSession ? "authenticated" : "unauthenticated",
79127
session: localSession,
80128
refresh,
129+
refetch: fetchSession,
81130
}}
82131
>
83132
{children}

packages/payload-authjs/src/payload/session/PayloadSessionProviderWrapper.tsx

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)