@riktajs/react 0.11.5 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,226 +1,139 @@
1
- import { createContext, useState, useCallback, useEffect, useMemo, useContext, useRef } from 'react';
2
- import { jsx } from 'react/jsx-runtime';
1
+ import { useCallback, useState, useEffect, useRef } from 'react';
3
2
 
4
- // src/context/RouterContext.ts
5
- var defaultRouterContext = {
6
- pathname: "/",
7
- search: "",
8
- href: "/",
9
- navigate: () => {
10
- console.warn("[RiktaReact] Router context not initialized. Wrap your app with <RiktaProvider>");
11
- },
12
- params: {},
13
- setParams: () => {
14
- },
15
- isNavigating: false
16
- };
17
- var RouterContext = createContext(defaultRouterContext);
18
- RouterContext.displayName = "RiktaRouterContext";
19
- var SsrContext = createContext(null);
20
- SsrContext.displayName = "RiktaSsrContext";
21
- function getLocationInfo(ssrUrl) {
3
+ // src/utils/getSsrData.ts
4
+ var ssrDataCache = void 0;
5
+ function getSsrData() {
6
+ if (ssrDataCache !== void 0) {
7
+ return ssrDataCache;
8
+ }
22
9
  if (typeof window === "undefined") {
23
- if (ssrUrl) {
24
- try {
25
- const url = new URL(ssrUrl, "http://localhost");
26
- return {
27
- pathname: url.pathname,
28
- search: url.search.slice(1),
29
- href: ssrUrl
30
- };
31
- } catch {
32
- }
33
- }
34
- return { pathname: "/", search: "", href: "/" };
10
+ return null;
35
11
  }
36
- return {
37
- pathname: window.location.pathname,
38
- search: window.location.search.slice(1),
39
- // Remove leading ?
40
- href: window.location.href
41
- };
42
- }
43
- function getSsrData() {
44
- if (typeof window === "undefined") return void 0;
45
12
  const rawData = window.__SSR_DATA__;
46
- if (!rawData) return void 0;
13
+ if (!rawData) {
14
+ ssrDataCache = null;
15
+ return null;
16
+ }
17
+ ssrDataCache = rawData;
47
18
  return rawData;
48
19
  }
49
- async function fetchSsrData(url) {
50
- try {
51
- const response = await fetch(url, {
52
- headers: {
53
- "X-Rikta-Data": "1",
54
- "Accept": "application/json"
55
- }
56
- });
57
- if (response.ok) {
58
- const data = await response.json();
59
- return data;
20
+ function clearSsrDataCache() {
21
+ ssrDataCache = void 0;
22
+ }
23
+ function setSsrData(data) {
24
+ ssrDataCache = data;
25
+ }
26
+ function buildUrl(path, params) {
27
+ if (!params || Object.keys(params).length === 0) {
28
+ return path;
29
+ }
30
+ const url = new URL(path, window.location.origin);
31
+ for (const [key, value] of Object.entries(params)) {
32
+ if (value !== void 0 && value !== null) {
33
+ url.searchParams.set(key, String(value));
60
34
  }
61
- } catch (error) {
62
- console.warn("[RiktaReact] Failed to fetch SSR data:", error);
63
35
  }
64
- return null;
36
+ return url.pathname + url.search;
65
37
  }
66
- var RiktaProvider = ({
67
- ssrData: initialSsrData,
68
- initialParams = {},
69
- children
70
- }) => {
71
- const [ssrData, setSsrData] = useState(() => {
72
- return initialSsrData ?? getSsrData() ?? null;
73
- });
74
- const [isNavigating, setIsNavigating] = useState(false);
75
- const [location, setLocation] = useState(() => {
76
- const resolvedSsrData = initialSsrData ?? getSsrData();
77
- return getLocationInfo(resolvedSsrData?.url);
78
- });
79
- const [params, setParams] = useState(initialParams);
80
- const navigate = useCallback(async (url, options = {}) => {
81
- const { replace = false, scroll = true, state } = options;
38
+ function useNavigate() {
39
+ const navigate = useCallback((path, paramsOrOptions, maybeOptions) => {
82
40
  if (typeof window === "undefined") return;
83
- let targetUrl;
84
- try {
85
- targetUrl = new URL(url, window.location.origin);
86
- } catch {
87
- console.error(`[RiktaReact] Invalid URL: ${url}`);
88
- return;
89
- }
90
- if (targetUrl.origin !== window.location.origin) {
91
- window.location.href = url;
92
- return;
93
- }
94
- setIsNavigating(true);
95
- const newSsrData = await fetchSsrData(targetUrl.href);
96
- if (newSsrData) {
97
- setSsrData(newSsrData);
98
- }
99
- if (replace) {
100
- window.history.replaceState(state ?? null, "", targetUrl.href);
41
+ let url;
42
+ let options;
43
+ if (paramsOrOptions && typeof paramsOrOptions === "object") {
44
+ const keys = Object.keys(paramsOrOptions);
45
+ const isOptions = keys.length === 0 || keys.length === 1 && keys[0] === "replace";
46
+ if (isOptions && !maybeOptions) {
47
+ url = path;
48
+ options = paramsOrOptions;
49
+ } else {
50
+ url = buildUrl(path, paramsOrOptions);
51
+ options = maybeOptions;
52
+ }
101
53
  } else {
102
- window.history.pushState(state ?? null, "", targetUrl.href);
54
+ url = path;
55
+ options = void 0;
103
56
  }
104
- setLocation({
105
- pathname: targetUrl.pathname,
106
- search: targetUrl.search.slice(1),
107
- href: targetUrl.href
108
- });
109
- if (scroll) {
110
- window.scrollTo(0, 0);
57
+ if (options?.replace) {
58
+ window.location.replace(url);
59
+ } else {
60
+ window.location.href = url;
111
61
  }
112
- setIsNavigating(false);
113
62
  }, []);
114
- useEffect(() => {
115
- if (typeof window === "undefined") return;
116
- const handlePopState = async () => {
117
- const newLocation = getLocationInfo();
118
- setIsNavigating(true);
119
- const newSsrData = await fetchSsrData(newLocation.href);
120
- if (newSsrData) {
121
- setSsrData(newSsrData);
122
- }
123
- setLocation(newLocation);
124
- setIsNavigating(false);
125
- };
126
- window.addEventListener("popstate", handlePopState);
127
- return () => window.removeEventListener("popstate", handlePopState);
128
- }, []);
129
- const routerValue = useMemo(() => ({
130
- pathname: location.pathname,
131
- search: location.search,
132
- href: location.href,
133
- navigate,
134
- params,
135
- setParams,
136
- isNavigating
137
- }), [location.pathname, location.search, location.href, navigate, params, isNavigating]);
138
- return /* @__PURE__ */ jsx(SsrContext.Provider, { value: ssrData, children: /* @__PURE__ */ jsx(RouterContext.Provider, { value: routerValue, children }) });
139
- };
140
- RiktaProvider.displayName = "RiktaProvider";
141
- function useNavigation() {
142
- const context = useContext(RouterContext);
143
- const navigate = useCallback(
144
- (url, options) => {
145
- context.navigate(url, options);
146
- },
147
- [context.navigate]
148
- );
149
- return {
150
- /** Navigate to a new URL */
151
- navigate,
152
- /** Current pathname */
153
- pathname: context.pathname,
154
- /** Current search string (without ?) */
155
- search: context.search,
156
- /** Full href */
157
- href: context.href
158
- };
63
+ return navigate;
159
64
  }
160
- var Link = ({
161
- href,
162
- replace = false,
163
- scroll = true,
164
- prefetch = false,
165
- state,
166
- children,
167
- onClick,
168
- ...restProps
169
- }) => {
170
- const { navigate } = useNavigation();
171
- const handleClick = useCallback(
172
- (e) => {
173
- onClick?.(e);
174
- if (e.defaultPrevented) return;
175
- if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
176
- if (e.button !== 0) return;
177
- const target = e.currentTarget.target;
178
- if (target && target !== "_self") return;
179
- try {
180
- const url = new URL(href, window.location.origin);
181
- if (url.origin !== window.location.origin) return;
182
- } catch {
183
- return;
184
- }
185
- e.preventDefault();
186
- navigate(href, { replace, scroll, state });
187
- },
188
- [href, replace, scroll, state, navigate, onClick]
189
- );
190
- return /* @__PURE__ */ jsx("a", { href, onClick: handleClick, ...restProps, children });
191
- };
192
- Link.displayName = "Link";
65
+
66
+ // src/hooks/useParams.ts
193
67
  function useParams() {
194
- const context = useContext(RouterContext);
195
- return context.params;
68
+ const ssrData = getSsrData();
69
+ const params = ssrData?.meta?.params ?? {};
70
+ return params;
196
71
  }
197
72
  function useSearchParams() {
198
- const context = useContext(RouterContext);
199
- const searchParams = useMemo(() => {
200
- return new URLSearchParams(context.search);
201
- }, [context.search]);
202
- const setSearchParams = useMemo(() => {
203
- return (params) => {
204
- const newParams = params instanceof URLSearchParams ? params : new URLSearchParams(params);
205
- const search = newParams.toString();
206
- const newUrl = search ? `${context.pathname}?${search}` : context.pathname;
207
- context.navigate(newUrl, { scroll: false });
208
- };
209
- }, [context.pathname, context.navigate]);
73
+ const getSearchParams = () => {
74
+ if (typeof window === "undefined") {
75
+ return new URLSearchParams();
76
+ }
77
+ return new URLSearchParams(window.location.search);
78
+ };
79
+ const searchParams = getSearchParams();
80
+ const setSearchParams = useCallback((params) => {
81
+ if (typeof window === "undefined") return;
82
+ let newParams;
83
+ if (params instanceof URLSearchParams) {
84
+ newParams = params;
85
+ } else {
86
+ newParams = new URLSearchParams();
87
+ for (const [key, value] of Object.entries(params)) {
88
+ if (value !== void 0 && value !== null) {
89
+ newParams.set(key, String(value));
90
+ }
91
+ }
92
+ }
93
+ const search = newParams.toString();
94
+ const pathname = window.location.pathname;
95
+ const newUrl = search ? `${pathname}?${search}` : pathname;
96
+ window.location.href = newUrl;
97
+ }, []);
210
98
  return [searchParams, setSearchParams];
211
99
  }
212
- function useLocation() {
213
- const context = useContext(RouterContext);
100
+
101
+ // src/hooks/useLocation.ts
102
+ function getLocation() {
103
+ const ssrData = getSsrData();
104
+ if (ssrData?.url) {
105
+ const url = new URL(ssrData.url, "http://localhost");
106
+ const search = url.search.slice(1);
107
+ return {
108
+ pathname: url.pathname,
109
+ search,
110
+ href: ssrData.url,
111
+ searchParams: new URLSearchParams(search)
112
+ };
113
+ }
114
+ if (typeof window !== "undefined") {
115
+ const search = window.location.search.slice(1);
116
+ return {
117
+ pathname: window.location.pathname,
118
+ search,
119
+ href: window.location.href,
120
+ searchParams: new URLSearchParams(search)
121
+ };
122
+ }
214
123
  return {
215
- pathname: context.pathname,
216
- search: context.search,
217
- href: context.href,
218
- searchParams: new URLSearchParams(context.search)
124
+ pathname: "/",
125
+ search: "",
126
+ href: "/",
127
+ searchParams: new URLSearchParams()
219
128
  };
220
129
  }
130
+ function useLocation() {
131
+ return getLocation();
132
+ }
133
+
134
+ // src/hooks/useSsrData.ts
221
135
  function useSsrData() {
222
- const context = useContext(SsrContext);
223
- return context;
136
+ return getSsrData();
224
137
  }
225
138
  function useHydration() {
226
139
  const isServer = typeof window === "undefined";
@@ -346,6 +259,6 @@ function useAction(url, options = {}) {
346
259
  };
347
260
  }
348
261
 
349
- export { Link, RiktaProvider, RouterContext, SsrContext, useAction, useFetch, useHydration, useLocation, useNavigation, useParams, useSearchParams, useSsrData };
262
+ export { clearSsrDataCache, getSsrData, setSsrData, useAction, useFetch, useHydration, useLocation, useNavigate, useParams, useSearchParams, useSsrData };
350
263
  //# sourceMappingURL=index.js.map
351
264
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context/RouterContext.ts","../src/context/SsrContext.ts","../src/components/RiktaProvider.tsx","../src/hooks/useNavigation.ts","../src/components/Link.tsx","../src/hooks/useParams.ts","../src/hooks/useSearchParams.ts","../src/hooks/useLocation.ts","../src/hooks/useSsrData.ts","../src/hooks/useHydration.ts","../src/hooks/useFetch.ts","../src/hooks/useAction.ts"],"names":["createContext","useCallback","jsx","useContext","useMemo","useState","useEffect","actionResult"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext } from 'react';\nimport type { RouterContextValue } from '../types.js';\n\n/**\n * Default router context value for when no provider is present\n */\nconst defaultRouterContext: RouterContextValue = {\n pathname: '/',\n search: '',\n href: '/',\n navigate: () => {\n console.warn('[RiktaReact] Router context not initialized. Wrap your app with <RiktaProvider>');\n },\n params: {},\n setParams: () => {},\n isNavigating: false,\n};\n\n/**\n * React context for router state\n * Provides navigation utilities and current location info\n */\nexport const RouterContext = createContext<RouterContextValue>(defaultRouterContext);\n\nRouterContext.displayName = 'RiktaRouterContext';\n","import { createContext } from 'react';\nimport type { SsrData } from '../types.js';\n\n/**\n * React context for SSR data\n * Holds the server-rendered data passed via window.__SSR_DATA__\n */\nexport const SsrContext = createContext<SsrData | null>(null);\n\nSsrContext.displayName = 'RiktaSsrContext';\n","import { useState, useCallback, useEffect, useMemo, type FC } from 'react';\nimport { RouterContext } from '../context/RouterContext.js';\nimport { SsrContext } from '../context/SsrContext.js';\nimport type { RiktaProviderProps, SsrData, NavigateOptions } from '../types.js';\n\n/**\n * Get current location from window or fallback for SSR\n */\nfunction getLocationInfo(ssrUrl?: string) {\n if (typeof window === 'undefined') {\n // During SSR, use the URL from ssrData if available\n if (ssrUrl) {\n try {\n const url = new URL(ssrUrl, 'http://localhost');\n return {\n pathname: url.pathname,\n search: url.search.slice(1),\n href: ssrUrl,\n };\n } catch {\n // Fall through to default\n }\n }\n return { pathname: '/', search: '', href: '/' };\n }\n return {\n pathname: window.location.pathname,\n search: window.location.search.slice(1), // Remove leading ?\n href: window.location.href,\n };\n}\n\n/**\n * Get SSR data from window\n * Server now puts data in normalized format: { data, url, title, description }\n */\nfunction getSsrData(): SsrData | undefined {\n if (typeof window === 'undefined') return undefined;\n \n const rawData = window.__SSR_DATA__ as Record<string, unknown> | undefined;\n if (!rawData) return undefined;\n \n // Data should already be in normalized format from entry-server\n return rawData as unknown as SsrData;\n}\n\n/**\n * Fetch SSR data from server for client-side navigation\n */\nasync function fetchSsrData(url: string): Promise<SsrData | null> {\n try {\n const response = await fetch(url, {\n headers: {\n 'X-Rikta-Data': '1',\n 'Accept': 'application/json',\n },\n });\n \n if (response.ok) {\n const data = await response.json();\n return data as SsrData;\n }\n } catch (error) {\n console.warn('[RiktaReact] Failed to fetch SSR data:', error);\n }\n return null;\n}\n\n/**\n * RiktaProvider - Main provider component for Rikta React utilities\n * \n * Provides routing context, SSR data, and navigation utilities to the app.\n * Automatically fetches new page data during client-side navigation.\n * \n * @example\n * ```tsx\n * // In entry-client.tsx\n * import { RiktaProvider } from '@riktajs/react';\n * \n * hydrateRoot(\n * document.getElementById('root')!,\n * <RiktaProvider>\n * <App />\n * </RiktaProvider>\n * );\n * ```\n */\nexport const RiktaProvider: FC<RiktaProviderProps> = ({\n ssrData: initialSsrData,\n initialParams = {},\n children,\n}) => {\n // Initialize SSR data from window or props\n const [ssrData, setSsrData] = useState<SsrData | null>(() => {\n return initialSsrData ?? getSsrData() ?? null;\n });\n\n // Loading state for navigation\n const [isNavigating, setIsNavigating] = useState(false);\n\n // Initialize location state - use URL from ssrData for SSR consistency\n const [location, setLocation] = useState(() => {\n const resolvedSsrData = initialSsrData ?? getSsrData();\n return getLocationInfo(resolvedSsrData?.url);\n });\n \n // Route params state\n const [params, setParams] = useState<Record<string, string>>(initialParams);\n\n /**\n * Navigate to a new URL using History API with data fetching\n */\n const navigate = useCallback(async (url: string, options: NavigateOptions = {}) => {\n const { replace = false, scroll = true, state } = options;\n\n if (typeof window === 'undefined') return;\n\n // Handle full URLs vs relative paths\n let targetUrl: URL;\n try {\n targetUrl = new URL(url, window.location.origin);\n } catch {\n console.error(`[RiktaReact] Invalid URL: ${url}`);\n return;\n }\n\n // Only handle same-origin navigation\n if (targetUrl.origin !== window.location.origin) {\n window.location.href = url;\n return;\n }\n\n // Start navigation\n setIsNavigating(true);\n\n // Fetch new SSR data from server BEFORE updating anything else\n const newSsrData = await fetchSsrData(targetUrl.href);\n\n // Update SSR data FIRST (before location changes)\n if (newSsrData) {\n setSsrData(newSsrData);\n }\n\n // Update history\n if (replace) {\n window.history.replaceState(state ?? null, '', targetUrl.href);\n } else {\n window.history.pushState(state ?? null, '', targetUrl.href);\n }\n\n // Update location state AFTER data is ready\n setLocation({\n pathname: targetUrl.pathname,\n search: targetUrl.search.slice(1),\n href: targetUrl.href,\n });\n\n // Scroll to top if requested\n if (scroll) {\n window.scrollTo(0, 0);\n }\n\n // End navigation\n setIsNavigating(false);\n }, []);\n\n // Listen for popstate (browser back/forward) and fetch data\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handlePopState = async () => {\n const newLocation = getLocationInfo();\n \n // Fetch data for the new URL FIRST\n setIsNavigating(true);\n const newSsrData = await fetchSsrData(newLocation.href);\n \n // Update SSR data BEFORE location\n if (newSsrData) {\n setSsrData(newSsrData);\n }\n \n // Then update location\n setLocation(newLocation);\n setIsNavigating(false);\n };\n\n window.addEventListener('popstate', handlePopState);\n return () => window.removeEventListener('popstate', handlePopState);\n }, []);\n\n // Memoize router context value\n const routerValue = useMemo(() => ({\n pathname: location.pathname,\n search: location.search,\n href: location.href,\n navigate,\n params,\n setParams,\n isNavigating,\n }), [location.pathname, location.search, location.href, navigate, params, isNavigating]);\n\n return (\n <SsrContext.Provider value={ssrData}>\n <RouterContext.Provider value={routerValue}>\n {children}\n </RouterContext.Provider>\n </SsrContext.Provider>\n );\n};\n\nRiktaProvider.displayName = 'RiktaProvider';\n","import { useContext, useCallback } from 'react';\nimport { RouterContext } from '../context/RouterContext.js';\nimport type { NavigateOptions } from '../types.js';\n\n/**\n * Hook for programmatic navigation\n * \n * @returns Object with navigate function and current location info\n * \n * @example\n * ```tsx\n * import { useNavigation } from '@riktajs/react';\n * \n * function MyComponent() {\n * const { navigate, pathname } = useNavigation();\n * \n * const handleSubmit = async () => {\n * await saveData();\n * navigate('/success');\n * };\n * \n * return (\n * <button onClick={handleSubmit}>\n * Submit (current path: {pathname})\n * </button>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // With options\n * const { navigate } = useNavigation();\n * \n * // Replace history entry (for redirects)\n * navigate('/login', { replace: true });\n * \n * // Don't scroll to top\n * navigate('/next', { scroll: false });\n * \n * // Pass state\n * navigate('/edit', { state: { from: 'list' } });\n * ```\n */\nexport function useNavigation() {\n const context = useContext(RouterContext);\n\n const navigate = useCallback(\n (url: string, options?: NavigateOptions) => {\n context.navigate(url, options);\n },\n [context.navigate]\n );\n\n return {\n /** Navigate to a new URL */\n navigate,\n /** Current pathname */\n pathname: context.pathname,\n /** Current search string (without ?) */\n search: context.search,\n /** Full href */\n href: context.href,\n };\n}\n","import { useCallback, type FC, type MouseEvent } from 'react';\nimport { useNavigation } from '../hooks/useNavigation.js';\nimport type { LinkProps } from '../types.js';\n\n/**\n * Link component for client-side navigation\n * \n * Renders an anchor tag that uses the History API for navigation\n * instead of causing a full page reload.\n * \n * @example\n * ```tsx\n * import { Link } from '@riktajs/react';\n * \n * function Nav() {\n * return (\n * <nav>\n * <Link href=\"/\">Home</Link>\n * <Link href=\"/about\">About</Link>\n * <Link href=\"/items/123\">Item 123</Link>\n * </nav>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // With options\n * <Link href=\"/dashboard\" replace scroll={false}>\n * Dashboard\n * </Link>\n * ```\n */\nexport const Link: FC<LinkProps> = ({\n href,\n replace = false,\n scroll = true,\n prefetch = false,\n state,\n children,\n onClick,\n ...restProps\n}) => {\n const { navigate } = useNavigation();\n\n const handleClick = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n // Call user's onClick handler if provided\n onClick?.(e);\n\n // Don't handle if default was prevented\n if (e.defaultPrevented) return;\n\n // Don't handle modified clicks (open in new tab, etc.)\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n\n // Don't handle right clicks\n if (e.button !== 0) return;\n\n // Don't handle target=\"_blank\" etc.\n const target = (e.currentTarget as HTMLAnchorElement).target;\n if (target && target !== '_self') return;\n\n // Don't handle external links\n try {\n const url = new URL(href, window.location.origin);\n if (url.origin !== window.location.origin) return;\n } catch {\n return;\n }\n\n // Handle the navigation\n e.preventDefault();\n navigate(href, { replace, scroll, state });\n },\n [href, replace, scroll, state, navigate, onClick]\n );\n\n // Prefetch support could be added here in the future\n // using <link rel=\"prefetch\"> or Intersection Observer\n\n return (\n <a href={href} onClick={handleClick} {...restProps}>\n {children}\n </a>\n );\n};\n\nLink.displayName = 'Link';\n","import { useContext } from 'react';\nimport { RouterContext } from '../context/RouterContext.js';\n\n/**\n * Hook to access route parameters\n * \n * Route parameters are extracted from the URL path by the server\n * and passed via SSR data. They're stored in the RouterContext.\n * \n * @returns Object with route parameter values\n * \n * @example\n * ```tsx\n * // For route /item/:id\n * import { useParams } from '@riktajs/react';\n * \n * function ItemPage() {\n * const { id } = useParams<{ id: string }>();\n * \n * return <h1>Item {id}</h1>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Multiple params - /users/:userId/posts/:postId\n * function PostPage() {\n * const { userId, postId } = useParams<{ userId: string; postId: string }>();\n * \n * return <h1>Post {postId} by User {userId}</h1>;\n * }\n * ```\n */\nexport function useParams<T extends Record<string, string> = Record<string, string>>(): T {\n const context = useContext(RouterContext);\n return context.params as T;\n}\n","import { useContext, useMemo } from 'react';\nimport { RouterContext } from '../context/RouterContext.js';\n\n/**\n * Hook to access and manipulate URL search parameters\n * \n * @returns Tuple of [URLSearchParams, setSearchParams function]\n * \n * @example\n * ```tsx\n * import { useSearchParams } from '@riktajs/react';\n * \n * function SearchPage() {\n * const [searchParams, setSearchParams] = useSearchParams();\n * const query = searchParams.get('q') ?? '';\n * const page = parseInt(searchParams.get('page') ?? '1', 10);\n * \n * const handleSearch = (newQuery: string) => {\n * setSearchParams({ q: newQuery, page: '1' });\n * };\n * \n * const handleNextPage = () => {\n * setSearchParams({ q: query, page: String(page + 1) });\n * };\n * \n * return (\n * <div>\n * <input \n * value={query} \n * onChange={(e) => handleSearch(e.target.value)} \n * />\n * <button onClick={handleNextPage}>Next Page</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useSearchParams(): [URLSearchParams, (params: Record<string, string> | URLSearchParams) => void] {\n const context = useContext(RouterContext);\n\n const searchParams = useMemo(() => {\n return new URLSearchParams(context.search);\n }, [context.search]);\n\n const setSearchParams = useMemo(() => {\n return (params: Record<string, string> | URLSearchParams) => {\n const newParams = params instanceof URLSearchParams \n ? params \n : new URLSearchParams(params);\n \n const search = newParams.toString();\n const newUrl = search \n ? `${context.pathname}?${search}` \n : context.pathname;\n \n context.navigate(newUrl, { scroll: false });\n };\n }, [context.pathname, context.navigate]);\n\n return [searchParams, setSearchParams];\n}\n","import { useContext } from 'react';\nimport { RouterContext } from '../context/RouterContext.js';\n\n/**\n * Location object returned by useLocation\n */\nexport interface Location {\n /** Current pathname (e.g., /items/123) */\n pathname: string;\n /** Current search string without ? (e.g., page=2&sort=asc) */\n search: string;\n /** Full href */\n href: string;\n /** Parsed search params */\n searchParams: URLSearchParams;\n}\n\n/**\n * Hook to access current location information\n * \n * @returns Location object with pathname, search, href, and searchParams\n * \n * @example\n * ```tsx\n * import { useLocation } from '@riktajs/react';\n * \n * function Breadcrumbs() {\n * const location = useLocation();\n * \n * return (\n * <nav>\n * Current path: {location.pathname}\n * {location.search && <span>?{location.search}</span>}\n * </nav>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Access search params\n * function FilterDisplay() {\n * const { searchParams } = useLocation();\n * const filter = searchParams.get('filter');\n * \n * return filter ? <span>Filtered by: {filter}</span> : null;\n * }\n * ```\n */\nexport function useLocation(): Location {\n const context = useContext(RouterContext);\n\n return {\n pathname: context.pathname,\n search: context.search,\n href: context.href,\n searchParams: new URLSearchParams(context.search),\n };\n}\n","import { useContext } from 'react';\nimport { SsrContext } from '../context/SsrContext.js';\nimport type { SsrData } from '../types.js';\n\n/**\n * Hook to access SSR data passed from server\n * \n * SSR data is passed via window.__SSR_DATA__ and contains\n * the initial data rendered on the server.\n * \n * @returns SSR data object or null if not available\n * \n * @example\n * ```tsx\n * import { useSsrData } from '@riktajs/react';\n * \n * interface PageData {\n * title: string;\n * items: Array<{ id: string; name: string }>;\n * }\n * \n * function ItemList() {\n * const ssrData = useSsrData<PageData>();\n * \n * if (!ssrData) {\n * return <div>Loading...</div>;\n * }\n * \n * return (\n * <div>\n * <h1>{ssrData.data.title}</h1>\n * <ul>\n * {ssrData.data.items.map(item => (\n * <li key={item.id}>{item.name}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Access just the data\n * function MyComponent() {\n * const ssrData = useSsrData<{ user: User }>();\n * const user = ssrData?.data.user;\n * \n * return user ? <UserProfile user={user} /> : <LoginPrompt />;\n * }\n * ```\n */\nexport function useSsrData<T = unknown>(): SsrData<T> | null {\n const context = useContext(SsrContext);\n return context as SsrData<T> | null;\n}\n","import { useState, useEffect } from 'react';\nimport type { HydrationState } from '../types.js';\n\n/**\n * Hook to track hydration state\n * \n * Useful for rendering different content during SSR vs after hydration,\n * or for avoiding hydration mismatches.\n * \n * @returns Hydration state object\n * \n * @example\n * ```tsx\n * import { useHydration } from '@riktajs/react';\n * \n * function TimeDisplay() {\n * const { isHydrated, isServer } = useHydration();\n * \n * // On server and initial render, show static content\n * // After hydration, show dynamic content\n * if (!isHydrated) {\n * return <span>Loading time...</span>;\n * }\n * \n * return <span>{new Date().toLocaleTimeString()}</span>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Avoid hydration mismatch with client-only content\n * function ClientOnlyComponent() {\n * const { isHydrated } = useHydration();\n * \n * if (!isHydrated) {\n * return null; // Or a placeholder\n * }\n * \n * return <SomeClientOnlyLibrary />;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Conditional rendering based on environment\n * function DebugPanel() {\n * const { isServer } = useHydration();\n * \n * // Never render on server, only after client hydration\n * if (isServer) return null;\n * \n * return <DevTools />;\n * }\n * ```\n */\nexport function useHydration(): HydrationState {\n const isServer = typeof window === 'undefined';\n const [isHydrated, setIsHydrated] = useState(false);\n\n useEffect(() => {\n setIsHydrated(true);\n }, []);\n\n return {\n isHydrated,\n isServer,\n };\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport type { FetchState } from '../types.js';\n\n/**\n * Options for useFetch hook\n */\nexport interface UseFetchOptions extends Omit<RequestInit, 'body'> {\n /** Skip initial fetch (useful for conditional fetching) */\n skip?: boolean;\n /** Dependencies that trigger refetch when changed */\n deps?: unknown[];\n /** Transform response before setting data */\n transform?: (data: unknown) => unknown;\n}\n\n/**\n * Hook for data fetching with loading and error states\n * \n * @param url URL to fetch from\n * @param options Fetch options\n * @returns Fetch state with data, loading, error, and refetch function\n * \n * @example\n * ```tsx\n * import { useFetch } from '@riktajs/react';\n * \n * interface User {\n * id: string;\n * name: string;\n * }\n * \n * function UserProfile({ userId }: { userId: string }) {\n * const { data, loading, error, refetch } = useFetch<User>(\n * `/api/users/${userId}`\n * );\n * \n * if (loading) return <Spinner />;\n * if (error) return <Error message={error} />;\n * if (!data) return null;\n * \n * return (\n * <div>\n * <h1>{data.name}</h1>\n * <button onClick={refetch}>Refresh</button>\n * </div>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // With options\n * const { data } = useFetch<Item[]>('/api/items', {\n * headers: { 'Authorization': `Bearer ${token}` },\n * deps: [token], // Refetch when token changes\n * skip: !token, // Don't fetch until we have a token\n * });\n * ```\n * \n * @example\n * ```tsx\n * // With transform\n * const { data } = useFetch<{ results: Item[] }>('/api/search', {\n * transform: (res) => res.results, // Extract just the results array\n * });\n * ```\n */\nexport function useFetch<T = unknown>(\n url: string,\n options: UseFetchOptions = {}\n): FetchState<T> {\n const { skip = false, deps = [], transform, ...fetchOptions } = options;\n \n const [state, setState] = useState<Omit<FetchState<T>, 'refetch'>>({\n data: null,\n loading: !skip,\n error: null,\n });\n\n // Use ref to track if component is mounted\n const mountedRef = useRef(true);\n // Use ref to track current fetch to handle race conditions\n const fetchIdRef = useRef(0);\n\n const doFetch = useCallback(async (ignoreSkip = false) => {\n if (skip && !ignoreSkip) return;\n\n const fetchId = ++fetchIdRef.current;\n setState(prev => ({ ...prev, loading: true, error: null }));\n\n try {\n const response = await fetch(url, fetchOptions);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n let data = await response.json();\n\n // Apply transform if provided\n if (transform) {\n data = transform(data);\n }\n\n // Only update state if this is still the current fetch and component is mounted\n if (fetchId === fetchIdRef.current && mountedRef.current) {\n setState({ data: data as T, loading: false, error: null });\n }\n } catch (err) {\n if (fetchId === fetchIdRef.current && mountedRef.current) {\n const message = err instanceof Error ? err.message : 'An error occurred';\n setState({ data: null, loading: false, error: message });\n }\n }\n }, [url, skip, JSON.stringify(fetchOptions), transform]);\n\n // Fetch on mount and when dependencies change\n useEffect(() => {\n mountedRef.current = true;\n doFetch();\n\n return () => {\n mountedRef.current = false;\n };\n }, [doFetch, ...deps]);\n\n // refetch ignores skip option to allow manual triggering\n const refetch = useCallback(async () => {\n await doFetch(true);\n }, [doFetch]);\n\n return {\n ...state,\n refetch,\n };\n}\n","import { useState, useCallback } from 'react';\nimport type { ActionResult, ActionState } from '../types.js';\n\n/**\n * Options for useAction hook\n */\nexport interface UseActionOptions<TResult = unknown> {\n /** Callback on successful action */\n onSuccess?: (result: TResult) => void;\n /** Callback on action error */\n onError?: (error: string) => void;\n /** HTTP method to use */\n method?: 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Additional headers */\n headers?: Record<string, string>;\n}\n\n/**\n * Hook for executing server actions (form submissions, mutations)\n * \n * @param url URL to send the action to\n * @param options Action options\n * @returns Action state with execute, pending, result, and reset\n * \n * @example\n * ```tsx\n * import { useAction } from '@riktajs/react';\n * \n * interface CreateItemInput {\n * name: string;\n * price: number;\n * }\n * \n * interface Item {\n * id: string;\n * name: string;\n * price: number;\n * }\n * \n * function CreateItemForm() {\n * const { execute, pending, result } = useAction<CreateItemInput, Item>(\n * '/api/items',\n * {\n * onSuccess: (item) => {\n * console.log('Created item:', item);\n * },\n * }\n * );\n * \n * const handleSubmit = async (e: FormEvent) => {\n * e.preventDefault();\n * const formData = new FormData(e.target as HTMLFormElement);\n * await execute({\n * name: formData.get('name') as string,\n * price: Number(formData.get('price')),\n * });\n * };\n * \n * return (\n * <form onSubmit={handleSubmit}>\n * <input name=\"name\" required />\n * <input name=\"price\" type=\"number\" required />\n * <button disabled={pending}>\n * {pending ? 'Creating...' : 'Create Item'}\n * </button>\n * {result?.error && <p className=\"error\">{result.error}</p>}\n * {result?.fieldErrors?.name && (\n * <p className=\"error\">{result.fieldErrors.name[0]}</p>\n * )}\n * </form>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // DELETE action\n * const { execute, pending } = useAction<{ id: string }, void>(\n * '/api/items',\n * { method: 'DELETE' }\n * );\n * \n * const handleDelete = () => execute({ id: itemId });\n * ```\n */\nexport function useAction<TInput = unknown, TResult = unknown>(\n url: string,\n options: UseActionOptions<TResult> = {}\n): ActionState<TInput, TResult> {\n const {\n onSuccess,\n onError,\n method = 'POST',\n headers: customHeaders = {},\n } = options;\n\n const [pending, setPending] = useState(false);\n const [result, setResult] = useState<ActionResult<TResult> | null>(null);\n\n const execute = useCallback(\n async (input: TInput): Promise<ActionResult<TResult>> => {\n setPending(true);\n setResult(null);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...customHeaders,\n },\n body: JSON.stringify(input),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n // Handle error response\n const actionResult: ActionResult<TResult> = {\n success: false,\n error: data.message || data.error || `HTTP ${response.status}`,\n fieldErrors: data.fieldErrors,\n };\n setResult(actionResult);\n onError?.(actionResult.error!);\n return actionResult;\n }\n\n // Handle success response\n const actionResult: ActionResult<TResult> = {\n success: true,\n data: data as TResult,\n };\n setResult(actionResult);\n onSuccess?.(data as TResult);\n return actionResult;\n } catch (err) {\n const message = err instanceof Error ? err.message : 'An error occurred';\n const actionResult: ActionResult<TResult> = {\n success: false,\n error: message,\n };\n setResult(actionResult);\n onError?.(message);\n return actionResult;\n } finally {\n setPending(false);\n }\n },\n [url, method, JSON.stringify(customHeaders), onSuccess, onError]\n );\n\n const reset = useCallback(() => {\n setResult(null);\n }, []);\n\n return {\n execute,\n pending,\n result,\n reset,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/getSsrData.ts","../src/hooks/useNavigate.ts","../src/hooks/useParams.ts","../src/hooks/useSearchParams.ts","../src/hooks/useLocation.ts","../src/hooks/useSsrData.ts","../src/hooks/useHydration.ts","../src/hooks/useFetch.ts","../src/hooks/useAction.ts"],"names":["useCallback","useState","useEffect","actionResult"],"mappings":"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","file":"index.js","sourcesContent":["import type { SsrData } from '../types.js';\n\n/**\n * Cache for SSR data to avoid repeated window access\n */\nlet ssrDataCache: SsrData | null | undefined = undefined;\n\n/**\n * Get SSR data from window.__SSR_DATA__\n * \n * This is a pure utility function (not a React hook) that reads\n * the SSR data passed from the server. It can be called anywhere\n * in your application without needing a Provider wrapper.\n * \n * The data is cached after first read for performance.\n * \n * @returns SSR data object or null if not available\n * \n * @example\n * ```tsx\n * import { getSsrData } from '@riktajs/react';\n * \n * interface PageData {\n * title: string;\n * items: Array<{ id: string; name: string }>;\n * }\n * \n * function ItemList() {\n * const ssrData = getSsrData<PageData>();\n * \n * if (!ssrData) {\n * return <div>Loading...</div>;\n * }\n * \n * return (\n * <div>\n * <h1>{ssrData.data.title}</h1>\n * <ul>\n * {ssrData.data.items.map(item => (\n * <li key={item.id}>{item.name}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Access directly without needing Provider\n * const ssrData = getSsrData<{ user: User }>();\n * const user = ssrData?.data.user;\n * ```\n */\nexport function getSsrData<T = unknown>(): SsrData<T> | null {\n // Return cached value if available\n if (ssrDataCache !== undefined) {\n return ssrDataCache as SsrData<T> | null;\n }\n\n // Server-side: no window\n if (typeof window === 'undefined') {\n return null;\n }\n\n const rawData = window.__SSR_DATA__ as SsrData<T> | undefined;\n \n if (!rawData) {\n ssrDataCache = null;\n return null;\n }\n\n ssrDataCache = rawData as SsrData;\n return rawData;\n}\n\n/**\n * Clear the SSR data cache\n * Useful for testing or when SSR data changes dynamically\n */\nexport function clearSsrDataCache(): void {\n ssrDataCache = undefined;\n}\n\n/**\n * Set SSR data manually (for testing or server-side rendering)\n * \n * @param data - SSR data to set, or null to clear\n */\nexport function setSsrData(data: SsrData | null): void {\n ssrDataCache = data;\n}\n","import { useCallback } from 'react';\n\n/**\n * Options for navigation\n */\nexport interface NavigateOptions {\n /** Replace current history entry instead of pushing (uses location.replace) */\n replace?: boolean;\n}\n\n/**\n * Navigate function type\n * \n * @param path - The path to navigate to\n * @param paramsOrOptions - Either query params object or navigate options\n * @param options - Navigate options (when params are provided)\n */\nexport type NavigateFn = {\n (path: string, options?: NavigateOptions): void;\n (path: string, params: Record<string, string | number | boolean | undefined | null>, options?: NavigateOptions): void;\n};\n\n/**\n * Build a URL with query parameters\n */\nfunction buildUrl(path: string, params?: Record<string, string | number | boolean | undefined | null>): string {\n if (!params || Object.keys(params).length === 0) {\n return path;\n }\n\n const url = new URL(path, window.location.origin);\n \n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n\n // Return relative URL (pathname + search)\n return url.pathname + url.search;\n}\n\n/**\n * Hook for programmatic navigation using native browser APIs\n * \n * This hook provides a simple way to navigate between pages using\n * standard browser navigation (full page loads for SSR pages).\n * \n * @returns Navigate function\n * \n * @example\n * ```tsx\n * import { useNavigate } from '@riktajs/react';\n * \n * function MyComponent() {\n * const navigate = useNavigate();\n * \n * const handleClick = () => {\n * // Simple navigation\n * navigate('/dashboard');\n * };\n * \n * const handleSearch = (query: string) => {\n * // Navigation with query params\n * navigate('/search', { q: query, page: 1 });\n * // Results in: /search?q=query&page=1\n * };\n * \n * const handleLogin = () => {\n * // Replace current history entry (for redirects)\n * navigate('/login', { replace: true });\n * };\n * \n * const handleFilter = (filter: string) => {\n * // Params + options\n * navigate('/items', { filter, sort: 'name' }, { replace: true });\n * };\n * \n * return <button onClick={handleClick}>Go to Dashboard</button>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // After form submission\n * function CreateItemForm() {\n * const navigate = useNavigate();\n * \n * const handleSubmit = async (data: FormData) => {\n * const result = await createItem(data);\n * if (result.success) {\n * navigate(`/items/${result.id}`);\n * }\n * };\n * \n * return <form onSubmit={handleSubmit}>...</form>;\n * }\n * ```\n */\nexport function useNavigate(): NavigateFn {\n const navigate = useCallback((\n path: string,\n paramsOrOptions?: Record<string, string | number | boolean | undefined | null> | NavigateOptions,\n maybeOptions?: NavigateOptions\n ) => {\n // Skip on server\n if (typeof window === 'undefined') return;\n\n let url: string;\n let options: NavigateOptions | undefined;\n\n // Determine if second argument is params or options\n if (paramsOrOptions && typeof paramsOrOptions === 'object') {\n // Check if it looks like NavigateOptions (has only 'replace' key)\n const keys = Object.keys(paramsOrOptions);\n const isOptions = keys.length === 0 || (keys.length === 1 && keys[0] === 'replace');\n \n if (isOptions && !maybeOptions) {\n // It's options\n url = path;\n options = paramsOrOptions as NavigateOptions;\n } else {\n // It's params\n url = buildUrl(path, paramsOrOptions as Record<string, string | number | boolean | undefined | null>);\n options = maybeOptions;\n }\n } else {\n url = path;\n options = undefined;\n }\n\n // Perform navigation using native browser API\n if (options?.replace) {\n window.location.replace(url);\n } else {\n window.location.href = url;\n }\n }, []) as NavigateFn;\n\n return navigate;\n}\n","import { getSsrData } from '../utils/getSsrData.js';\n\n/**\n * Hook to access route parameters from SSR data\n * \n * Route parameters are extracted from the URL path by the server\n * and passed via SSR data. This hook reads them from the SSR data.\n * \n * @returns Object with route parameter values\n * \n * @example\n * ```tsx\n * // For route /item/:id\n * import { useParams } from '@riktajs/react';\n * \n * function ItemPage() {\n * const { id } = useParams<{ id: string }>();\n * \n * return <h1>Item {id}</h1>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Multiple params - /users/:userId/posts/:postId\n * function PostPage() {\n * const { userId, postId } = useParams<{ userId: string; postId: string }>();\n * \n * return <h1>Post {postId} by User {userId}</h1>;\n * }\n * ```\n */\nexport function useParams<T extends Record<string, string> = Record<string, string>>(): T {\n const ssrData = getSsrData();\n \n // Extract params from SSR data meta\n const params = (ssrData?.meta?.params as T) ?? ({} as T);\n \n return params;\n}\n","import { useCallback } from 'react';\n\n/**\n * Hook to access and update URL search parameters using native browser APIs\n * \n * This hook provides a simple interface for reading and modifying URL query\n * parameters. When updating params, it triggers a full page navigation to\n * ensure SSR data is refreshed.\n * \n * @returns Tuple of [URLSearchParams, setSearchParams function]\n * \n * @example\n * ```tsx\n * import { useSearchParams } from '@riktajs/react';\n * \n * function SearchPage() {\n * const [searchParams, setSearchParams] = useSearchParams();\n * const query = searchParams.get('q') ?? '';\n * const page = parseInt(searchParams.get('page') ?? '1', 10);\n * \n * const handleSearch = (newQuery: string) => {\n * setSearchParams({ q: newQuery, page: '1' });\n * };\n * \n * const handleNextPage = () => {\n * setSearchParams({ q: query, page: String(page + 1) });\n * };\n * \n * return (\n * <div>\n * <input \n * value={query} \n * onChange={(e) => handleSearch(e.target.value)} \n * />\n * <button onClick={handleNextPage}>Next Page</button>\n * </div>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Merge with existing params\n * function FilterComponent() {\n * const [searchParams, setSearchParams] = useSearchParams();\n * \n * const updateFilter = (filter: string) => {\n * // Get current params and update\n * const params = Object.fromEntries(searchParams.entries());\n * setSearchParams({ ...params, filter });\n * };\n * \n * return (\n * <select onChange={(e) => updateFilter(e.target.value)}>\n * <option value=\"\">All</option>\n * <option value=\"active\">Active</option>\n * <option value=\"inactive\">Inactive</option>\n * </select>\n * );\n * }\n * ```\n */\nexport function useSearchParams(): [URLSearchParams, (params: Record<string, string | number | boolean | undefined | null> | URLSearchParams) => void] {\n // Get current search params\n const getSearchParams = (): URLSearchParams => {\n if (typeof window === 'undefined') {\n return new URLSearchParams();\n }\n return new URLSearchParams(window.location.search);\n };\n\n const searchParams = getSearchParams();\n\n const setSearchParams = useCallback((params: Record<string, string | number | boolean | undefined | null> | URLSearchParams) => {\n if (typeof window === 'undefined') return;\n\n let newParams: URLSearchParams;\n \n if (params instanceof URLSearchParams) {\n newParams = params;\n } else {\n newParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n newParams.set(key, String(value));\n }\n }\n }\n \n const search = newParams.toString();\n const pathname = window.location.pathname;\n const newUrl = search ? `${pathname}?${search}` : pathname;\n \n // Navigate using native browser API (full page load for SSR)\n window.location.href = newUrl;\n }, []);\n\n return [searchParams, setSearchParams];\n}\n","import { getSsrData } from '../utils/getSsrData.js';\n\n/**\n * SSR data type with optional url field\n */\ninterface SsrDataWithUrl {\n url?: string;\n [key: string]: unknown;\n}\n\n/**\n * Location object returned by useLocation\n */\nexport interface Location {\n /** Current pathname (e.g., /items/123) */\n pathname: string;\n /** Current search string without ? (e.g., page=2&sort=asc) */\n search: string;\n /** Full href */\n href: string;\n /** Parsed search params */\n searchParams: URLSearchParams;\n}\n\n/**\n * Get location info from SSR data or window\n * Uses SSR data first to ensure hydration consistency\n */\nfunction getLocation(): Location {\n // First try to get URL from SSR data (works both server and client)\n const ssrData = getSsrData<SsrDataWithUrl>();\n \n if (ssrData?.url) {\n const url = new URL(ssrData.url, 'http://localhost');\n const search = url.search.slice(1); // Remove leading ?\n return {\n pathname: url.pathname,\n search,\n href: ssrData.url,\n searchParams: new URLSearchParams(search),\n };\n }\n\n // Fallback to window.location on client\n if (typeof window !== 'undefined') {\n const search = window.location.search.slice(1); // Remove leading ?\n return {\n pathname: window.location.pathname,\n search,\n href: window.location.href,\n searchParams: new URLSearchParams(search),\n };\n }\n\n // SSR fallback when no data available\n return {\n pathname: '/',\n search: '',\n href: '/',\n searchParams: new URLSearchParams(),\n };\n}\n\n/**\n * Hook to access current location information using native browser APIs\n * \n * This hook reads directly from `window.location` and provides a convenient\n * interface for accessing URL information.\n * \n * @returns Location object with pathname, search, href, and searchParams\n * \n * @example\n * ```tsx\n * import { useLocation } from '@riktajs/react';\n * \n * function Breadcrumbs() {\n * const location = useLocation();\n * \n * return (\n * <nav>\n * Current path: {location.pathname}\n * {location.search && <span>?{location.search}</span>}\n * </nav>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Access search params\n * function FilterDisplay() {\n * const { searchParams } = useLocation();\n * const filter = searchParams.get('filter');\n * \n * return filter ? <span>Filtered by: {filter}</span> : null;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Use pathname for conditional rendering\n * function Navigation() {\n * const { pathname } = useLocation();\n * \n * return (\n * <nav>\n * <a href=\"/\" className={pathname === '/' ? 'active' : ''}>Home</a>\n * <a href=\"/about\" className={pathname === '/about' ? 'active' : ''}>About</a>\n * </nav>\n * );\n * }\n * ```\n */\nexport function useLocation(): Location {\n return getLocation();\n}\n","import { getSsrData } from '../utils/getSsrData.js';\nimport type { SsrData } from '../types.js';\n\n/**\n * Hook to access SSR data passed from server\n * \n * SSR data is passed via window.__SSR_DATA__ and contains\n * the initial data rendered on the server.\n * \n * Note: This is a simple wrapper around getSsrData() for\n * familiarity. You can also use getSsrData() directly.\n * \n * @returns SSR data object or null if not available\n * \n * @example\n * ```tsx\n * import { useSsrData } from '@riktajs/react';\n * \n * interface PageData {\n * title: string;\n * items: Array<{ id: string; name: string }>;\n * }\n * \n * function ItemList() {\n * const ssrData = useSsrData<PageData>();\n * \n * if (!ssrData) {\n * return <div>Loading...</div>;\n * }\n * \n * return (\n * <div>\n * <h1>{ssrData.data.title}</h1>\n * <ul>\n * {ssrData.data.items.map(item => (\n * <li key={item.id}>{item.name}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Access just the data\n * function MyComponent() {\n * const ssrData = useSsrData<{ user: User }>();\n * const user = ssrData?.data.user;\n * \n * return user ? <UserProfile user={user} /> : <LoginPrompt />;\n * }\n * ```\n */\nexport function useSsrData<T = unknown>(): SsrData<T> | null {\n return getSsrData<T>();\n}\n","import { useState, useEffect } from 'react';\nimport type { HydrationState } from '../types.js';\n\n/**\n * Hook to track hydration state\n * \n * Useful for rendering different content during SSR vs after hydration,\n * or for avoiding hydration mismatches.\n * \n * @returns Hydration state object\n * \n * @example\n * ```tsx\n * import { useHydration } from '@riktajs/react';\n * \n * function TimeDisplay() {\n * const { isHydrated, isServer } = useHydration();\n * \n * // On server and initial render, show static content\n * // After hydration, show dynamic content\n * if (!isHydrated) {\n * return <span>Loading time...</span>;\n * }\n * \n * return <span>{new Date().toLocaleTimeString()}</span>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Avoid hydration mismatch with client-only content\n * function ClientOnlyComponent() {\n * const { isHydrated } = useHydration();\n * \n * if (!isHydrated) {\n * return null; // Or a placeholder\n * }\n * \n * return <SomeClientOnlyLibrary />;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Conditional rendering based on environment\n * function DebugPanel() {\n * const { isServer } = useHydration();\n * \n * // Never render on server, only after client hydration\n * if (isServer) return null;\n * \n * return <DevTools />;\n * }\n * ```\n */\nexport function useHydration(): HydrationState {\n const isServer = typeof window === 'undefined';\n const [isHydrated, setIsHydrated] = useState(false);\n\n useEffect(() => {\n setIsHydrated(true);\n }, []);\n\n return {\n isHydrated,\n isServer,\n };\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport type { FetchState } from '../types.js';\n\n/**\n * Options for useFetch hook\n */\nexport interface UseFetchOptions extends Omit<RequestInit, 'body'> {\n /** Skip initial fetch (useful for conditional fetching) */\n skip?: boolean;\n /** Dependencies that trigger refetch when changed */\n deps?: unknown[];\n /** Transform response before setting data */\n transform?: (data: unknown) => unknown;\n}\n\n/**\n * Hook for data fetching with loading and error states\n * \n * @param url URL to fetch from\n * @param options Fetch options\n * @returns Fetch state with data, loading, error, and refetch function\n * \n * @example\n * ```tsx\n * import { useFetch } from '@riktajs/react';\n * \n * interface User {\n * id: string;\n * name: string;\n * }\n * \n * function UserProfile({ userId }: { userId: string }) {\n * const { data, loading, error, refetch } = useFetch<User>(\n * `/api/users/${userId}`\n * );\n * \n * if (loading) return <Spinner />;\n * if (error) return <Error message={error} />;\n * if (!data) return null;\n * \n * return (\n * <div>\n * <h1>{data.name}</h1>\n * <button onClick={refetch}>Refresh</button>\n * </div>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // With options\n * const { data } = useFetch<Item[]>('/api/items', {\n * headers: { 'Authorization': `Bearer ${token}` },\n * deps: [token], // Refetch when token changes\n * skip: !token, // Don't fetch until we have a token\n * });\n * ```\n * \n * @example\n * ```tsx\n * // With transform\n * const { data } = useFetch<{ results: Item[] }>('/api/search', {\n * transform: (res) => res.results, // Extract just the results array\n * });\n * ```\n */\nexport function useFetch<T = unknown>(\n url: string,\n options: UseFetchOptions = {}\n): FetchState<T> {\n const { skip = false, deps = [], transform, ...fetchOptions } = options;\n \n const [state, setState] = useState<Omit<FetchState<T>, 'refetch'>>({\n data: null,\n loading: !skip,\n error: null,\n });\n\n // Use ref to track if component is mounted\n const mountedRef = useRef(true);\n // Use ref to track current fetch to handle race conditions\n const fetchIdRef = useRef(0);\n\n const doFetch = useCallback(async (ignoreSkip = false) => {\n if (skip && !ignoreSkip) return;\n\n const fetchId = ++fetchIdRef.current;\n setState(prev => ({ ...prev, loading: true, error: null }));\n\n try {\n const response = await fetch(url, fetchOptions);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n let data = await response.json();\n\n // Apply transform if provided\n if (transform) {\n data = transform(data);\n }\n\n // Only update state if this is still the current fetch and component is mounted\n if (fetchId === fetchIdRef.current && mountedRef.current) {\n setState({ data: data as T, loading: false, error: null });\n }\n } catch (err) {\n if (fetchId === fetchIdRef.current && mountedRef.current) {\n const message = err instanceof Error ? err.message : 'An error occurred';\n setState({ data: null, loading: false, error: message });\n }\n }\n }, [url, skip, JSON.stringify(fetchOptions), transform]);\n\n // Fetch on mount and when dependencies change\n useEffect(() => {\n mountedRef.current = true;\n doFetch();\n\n return () => {\n mountedRef.current = false;\n };\n }, [doFetch, ...deps]);\n\n // refetch ignores skip option to allow manual triggering\n const refetch = useCallback(async () => {\n await doFetch(true);\n }, [doFetch]);\n\n return {\n ...state,\n refetch,\n };\n}\n","import { useState, useCallback } from 'react';\nimport type { ActionResult, ActionState } from '../types.js';\n\n/**\n * Options for useAction hook\n */\nexport interface UseActionOptions<TResult = unknown> {\n /** Callback on successful action */\n onSuccess?: (result: TResult) => void;\n /** Callback on action error */\n onError?: (error: string) => void;\n /** HTTP method to use */\n method?: 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Additional headers */\n headers?: Record<string, string>;\n}\n\n/**\n * Hook for executing server actions (form submissions, mutations)\n * \n * @param url URL to send the action to\n * @param options Action options\n * @returns Action state with execute, pending, result, and reset\n * \n * @example\n * ```tsx\n * import { useAction } from '@riktajs/react';\n * \n * interface CreateItemInput {\n * name: string;\n * price: number;\n * }\n * \n * interface Item {\n * id: string;\n * name: string;\n * price: number;\n * }\n * \n * function CreateItemForm() {\n * const { execute, pending, result } = useAction<CreateItemInput, Item>(\n * '/api/items',\n * {\n * onSuccess: (item) => {\n * console.log('Created item:', item);\n * },\n * }\n * );\n * \n * const handleSubmit = async (e: FormEvent) => {\n * e.preventDefault();\n * const formData = new FormData(e.target as HTMLFormElement);\n * await execute({\n * name: formData.get('name') as string,\n * price: Number(formData.get('price')),\n * });\n * };\n * \n * return (\n * <form onSubmit={handleSubmit}>\n * <input name=\"name\" required />\n * <input name=\"price\" type=\"number\" required />\n * <button disabled={pending}>\n * {pending ? 'Creating...' : 'Create Item'}\n * </button>\n * {result?.error && <p className=\"error\">{result.error}</p>}\n * {result?.fieldErrors?.name && (\n * <p className=\"error\">{result.fieldErrors.name[0]}</p>\n * )}\n * </form>\n * );\n * }\n * ```\n * \n * @example\n * ```tsx\n * // DELETE action\n * const { execute, pending } = useAction<{ id: string }, void>(\n * '/api/items',\n * { method: 'DELETE' }\n * );\n * \n * const handleDelete = () => execute({ id: itemId });\n * ```\n */\nexport function useAction<TInput = unknown, TResult = unknown>(\n url: string,\n options: UseActionOptions<TResult> = {}\n): ActionState<TInput, TResult> {\n const {\n onSuccess,\n onError,\n method = 'POST',\n headers: customHeaders = {},\n } = options;\n\n const [pending, setPending] = useState(false);\n const [result, setResult] = useState<ActionResult<TResult> | null>(null);\n\n const execute = useCallback(\n async (input: TInput): Promise<ActionResult<TResult>> => {\n setPending(true);\n setResult(null);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...customHeaders,\n },\n body: JSON.stringify(input),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n // Handle error response\n const actionResult: ActionResult<TResult> = {\n success: false,\n error: data.message || data.error || `HTTP ${response.status}`,\n fieldErrors: data.fieldErrors,\n };\n setResult(actionResult);\n onError?.(actionResult.error!);\n return actionResult;\n }\n\n // Handle success response\n const actionResult: ActionResult<TResult> = {\n success: true,\n data: data as TResult,\n };\n setResult(actionResult);\n onSuccess?.(data as TResult);\n return actionResult;\n } catch (err) {\n const message = err instanceof Error ? err.message : 'An error occurred';\n const actionResult: ActionResult<TResult> = {\n success: false,\n error: message,\n };\n setResult(actionResult);\n onError?.(message);\n return actionResult;\n } finally {\n setPending(false);\n }\n },\n [url, method, JSON.stringify(customHeaders), onSuccess, onError]\n );\n\n const reset = useCallback(() => {\n setResult(null);\n }, []);\n\n return {\n execute,\n pending,\n result,\n reset,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riktajs/react",
3
- "version": "0.11.5",
3
+ "version": "0.11.6",
4
4
  "description": "React utilities for Rikta SSR framework - hooks and components for routing, data fetching, and server interaction",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",