diff --git a/examples/basic-nextjs/src/app/authors/page.tsx b/examples/basic-nextjs/src/app/authors/page.tsx
index 75c80c9..003b351 100644
--- a/examples/basic-nextjs/src/app/authors/page.tsx
+++ b/examples/basic-nextjs/src/app/authors/page.tsx
@@ -4,6 +4,7 @@ import Fallback from '@/components/Fallback';
import { getRowndUser } from '../../../../../src/next/server';
import { withRowndRequireSignIn } from '../../../../../src/next';
import { cookies } from 'next/headers';
+import Link from 'next/link';
async function Authors() {
const data = await fetch('https://jsonplaceholder.typicode.com/posts');
@@ -14,6 +15,7 @@ async function Authors() {
Authors
User ID: {JSON.stringify(user, null, 2)}
+
Home
{posts
diff --git a/examples/basic/package-lock.json b/examples/basic/package-lock.json
index 245ccdf..79857d9 100644
--- a/examples/basic/package-lock.json
+++ b/examples/basic/package-lock.json
@@ -10,7 +10,8 @@
"dependencies": {
"@rownd/react": "file:../../packages/react/rownd-react-2.2.2.tgz",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^7.0.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
@@ -747,6 +748,11 @@
"integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==",
"dev": true
},
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
+ },
"node_modules/@types/json-schema": {
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
@@ -1145,6 +1151,14 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2111,6 +2125,44 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz",
+ "integrity": "sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==",
+ "dependencies": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.0.1.tgz",
+ "integrity": "sha512-duBzwAAiIabhFPZfDjcYpJ+f08TMbPMETgq254GWne2NW1ZwRHhZLj7tpSp8KGb7JvZzlLcjGUnqLxpZQVEPng==",
+ "dependencies": {
+ "react-router": "7.0.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2207,6 +2259,11 @@
"node": ">=10"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2312,6 +2369,11 @@
"typescript": ">=4.2.0"
}
},
+ "node_modules/turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -2801,6 +2863,11 @@
"integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==",
"dev": true
},
+ "@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
+ },
"@types/json-schema": {
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
@@ -3068,6 +3135,11 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3766,6 +3838,25 @@
"scheduler": "^0.23.0"
}
},
+ "react-router": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz",
+ "integrity": "sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==",
+ "requires": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ }
+ },
+ "react-router-dom": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.0.1.tgz",
+ "integrity": "sha512-duBzwAAiIabhFPZfDjcYpJ+f08TMbPMETgq254GWne2NW1ZwRHhZLj7tpSp8KGb7JvZzlLcjGUnqLxpZQVEPng==",
+ "requires": {
+ "react-router": "7.0.1"
+ }
+ },
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3822,6 +3913,11 @@
"lru-cache": "^6.0.0"
}
},
+ "set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3895,6 +3991,11 @@
"dev": true,
"requires": {}
},
+ "turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/examples/basic/package.json b/examples/basic/package.json
index 8e4233b..7cea34f 100644
--- a/examples/basic/package.json
+++ b/examples/basic/package.json
@@ -12,7 +12,8 @@
"dependencies": {
"@rownd/react": "file:../../packages/react/rownd-react-2.2.2.tgz",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^7.0.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
diff --git a/examples/basic/src/App.tsx b/examples/basic/src/App.tsx
index 131608a..de7383c 100644
--- a/examples/basic/src/App.tsx
+++ b/examples/basic/src/App.tsx
@@ -4,6 +4,7 @@ import {
useRownd,
} from '../../../src/context/index';
import './App.css';
+import { Link } from 'react-router-dom';
const Initializing = () => Initializing...
;
@@ -23,6 +24,7 @@ function App() {
return (
}>
Rownd sample app
+ Go to profile
);
diff --git a/examples/basic/src/main.tsx b/examples/basic/src/main.tsx
index 5cc3bc7..5350c5a 100644
--- a/examples/basic/src/main.tsx
+++ b/examples/basic/src/main.tsx
@@ -3,11 +3,31 @@ import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { RowndProvider } from '../../../src/context/index';
+import {
+ createBrowserRouter,
+ Link,
+ RouterProvider,
+} from "react-router-dom";
+
+const Profile: React.FC = () => {
+ return Go back;
+};
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ },
+ {
+ path: "/profile",
+ element: ,
+ }
+]);
ReactDOM.createRoot(document.getElementById('root')!).render(
-
+
);
diff --git a/examples/remix/app/entry.server.tsx b/examples/remix/app/entry.server.tsx
index 3f41f89..e2181d5 100644
--- a/examples/remix/app/entry.server.tsx
+++ b/examples/remix/app/entry.server.tsx
@@ -11,7 +11,7 @@ import { createReadableStreamFromReadable } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { isbot } from 'isbot';
import { renderToPipeableStream } from 'react-dom/server';
-import { withRowndHandleRequest } from '../../../src/remix';
+import { withRowndHandleRequest } from '../../../src/remix/index';
const ABORT_DELAY = 5_000;
diff --git a/examples/remix/app/root.tsx b/examples/remix/app/root.tsx
index 099435a..beec748 100644
--- a/examples/remix/app/root.tsx
+++ b/examples/remix/app/root.tsx
@@ -9,7 +9,7 @@ import {
import type { LinksFunction } from '@remix-run/node';
import './tailwind.css';
-import { RemixRowndProvider } from '../../../src/remix';
+import { RemixRowndProvider } from '../../../src/remix/index';
export const loader = async (): Promise<{
env: {
diff --git a/examples/remix/app/routes/_index.tsx b/examples/remix/app/routes/_index.tsx
index 9c80dd0..2ff3332 100644
--- a/examples/remix/app/routes/_index.tsx
+++ b/examples/remix/app/routes/_index.tsx
@@ -1,6 +1,6 @@
import type { MetaFunction } from '@remix-run/node';
import { useNavigate } from '@remix-run/react';
-import { useRownd } from '../../../../src/remix';
+import { useRownd } from '../../../../src/remix/index';
import { useEffect } from 'react';
export const meta: MetaFunction = () => {
diff --git a/examples/remix/app/routes/books/index.tsx b/examples/remix/app/routes/books/index.tsx
index 31a14a6..eb522d6 100644
--- a/examples/remix/app/routes/books/index.tsx
+++ b/examples/remix/app/routes/books/index.tsx
@@ -4,7 +4,7 @@ import {
useRownd,
withRowndLoader,
withRowndRequireSignIn,
-} from '../../../../../src/remix';
+} from '../../../../../src/remix/index';
import { isAuthenticated, getRowndUser, getRowndUserId } from '../../../../../src/remix/server';
diff --git a/examples/remix/app/routes/profile/index.tsx b/examples/remix/app/routes/profile/index.tsx
index 8043d81..c517699 100644
--- a/examples/remix/app/routes/profile/index.tsx
+++ b/examples/remix/app/routes/profile/index.tsx
@@ -4,7 +4,7 @@ import {
useRownd,
withRowndRequireSignIn,
withRowndLoader,
-} from '../../../../../src/remix';
+} from '../../../../../src/remix/index';
type LoaderResponse = {
user_id: string;
diff --git a/src/context/RowndContext.tsx b/src/context/RowndContext.tsx
index 0eab78d..c48504f 100644
--- a/src/context/RowndContext.tsx
+++ b/src/context/RowndContext.tsx
@@ -1,5 +1,10 @@
-import React, { useContext, createContext } from 'react';
-import { TRowndContext } from './types';
+import React, {
+ useContext,
+ createContext,
+ useRef,
+ useCallback,
+} from 'react';
+import { TRowndContext, Unsubscribe, UserDataContext } from './types';
export const RowndContext = createContext(undefined);
@@ -25,7 +30,30 @@ function useRownd(): TRowndContext {
throw new Error('useRownd must be used within a RowndProvider');
}
- return context;
+ // If the user is authenticated on the first mount, we want to call the callback immediately
+ const isFirstMount = useRef(true);
+ const onAuthenticated = useCallback(
+ (callback: (userData: UserDataContext) => void): Unsubscribe => {
+ if (
+ context.is_authenticated &&
+ !context.is_initializing &&
+ context.user.data.user_id &&
+ isFirstMount.current
+ ) {
+ callback(context.user.data);
+ isFirstMount.current = false;
+ return () => {};
+ }
+ isFirstMount.current = false;
+ return context.onAuthenticated(callback);
+ },
+ [context.is_authenticated, context.is_initializing, context.user?.data?.user_id]
+ );
+
+ return {
+ ...context,
+ onAuthenticated,
+ };
}
export { useRownd };
diff --git a/src/context/types.ts b/src/context/types.ts
index 6adf060..b7d78e4 100644
--- a/src/context/types.ts
+++ b/src/context/types.ts
@@ -1,6 +1,6 @@
type AuthLevel = 'instant' | 'guest' | 'unverified' | 'verified';
-type Unsubscribe = () => void;
+export type Unsubscribe = () => void;
export type TRowndContext = {
requestSignIn: (e?: SignInProps) => void;
diff --git a/src/next/client/useRownd.tsx b/src/next/client/useRownd.tsx
index 49ace80..9855240 100644
--- a/src/next/client/useRownd.tsx
+++ b/src/next/client/useRownd.tsx
@@ -1,17 +1,29 @@
+'use client';
+
import { useStore } from './store/useStore';
import { store } from './store';
import { TRowndContext, UserDataContext } from '../../context/types';
-import { useCallback, useMemo } from 'react';
+import { useCallback, useMemo, useRef } from 'react';
import { setCookie } from '../../ssr/server/cookie';
import { addOnAuthenticatedListener, unsubscribeOnAuthenticatedListener } from '../../utils/listeners';
export const useRownd = (): TRowndContext => {
const state = useStore(store, (x) => x);
+ const isFirstMount = useRef(true);
const onAuthenticated: (
callback: (userData: UserDataContext) => void
) => () => void = useCallback(
(callback: (userData: UserDataContext) => void) => {
+
+ // If the user is authenticated on the first mount, we want to call the callback immediately
+ if (state.is_authenticated && !state.is_initializing && Boolean(state.user.data.user_id) && isFirstMount.current) {
+ callback(state.user.data);
+ isFirstMount.current = false;
+ return () => {};
+ }
+
+ isFirstMount.current = false;
const id = addOnAuthenticatedListener(callback);
const unsubscribe = () => {
@@ -20,7 +32,7 @@ export const useRownd = (): TRowndContext => {
return unsubscribe;
},
- []
+ [state.is_authenticated, state.is_initializing, state.user?.data?.user_id]
);
const memoized = useMemo(() => {
diff --git a/src/remix/client/useRownd.tsx b/src/remix/client/useRownd.tsx
index 490d825..1656d8d 100644
--- a/src/remix/client/useRownd.tsx
+++ b/src/remix/client/useRownd.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useMemo } from 'react';
+import { useCallback, useMemo, useRef } from 'react';
import { TRowndContext, UserDataContext } from '../../context/types';
import { useRownd as useRowndDefault } from '../../index';
import { setCookie } from '../../ssr/server/cookie';
@@ -7,10 +7,20 @@ import { addOnAuthenticatedListener, unsubscribeOnAuthenticatedListener } from '
const useRownd = (): TRowndContext => {
const rowndDefault = useRowndDefault();
+ const isFirstMount = useRef(true);
const onAuthenticated: (
callback: (userData: UserDataContext) => void
) => () => void = useCallback(
(callback: (userData: UserDataContext) => void) => {
+
+ // If the user is authenticated on the first mount, we want to call the callback immediately
+ if (rowndDefault.is_authenticated && !rowndDefault.is_initializing && Boolean(rowndDefault.user.data.user_id) && isFirstMount.current) {
+ callback(rowndDefault.user.data);
+ isFirstMount.current = false;
+ return () => {};
+ }
+ isFirstMount.current = false;
+
const id = addOnAuthenticatedListener(callback);
const unsubscribe = () => {
@@ -19,7 +29,7 @@ const useRownd = (): TRowndContext => {
return unsubscribe;
},
- []
+ [rowndDefault.is_authenticated, rowndDefault.is_initializing, rowndDefault.user.data.user_id]
);
const memoized = useMemo(