@riktajs/react 0.10.3

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 ADDED
@@ -0,0 +1,303 @@
1
+ import { createContext, useState, useCallback, useEffect, useMemo, useContext, useRef } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
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
+ };
16
+ var RouterContext = createContext(defaultRouterContext);
17
+ RouterContext.displayName = "RiktaRouterContext";
18
+ var SsrContext = createContext(null);
19
+ SsrContext.displayName = "RiktaSsrContext";
20
+ function getLocationInfo() {
21
+ if (typeof window === "undefined") {
22
+ return { pathname: "/", search: "", href: "/" };
23
+ }
24
+ return {
25
+ pathname: window.location.pathname,
26
+ search: window.location.search.slice(1),
27
+ // Remove leading ?
28
+ href: window.location.href
29
+ };
30
+ }
31
+ function getSsrData() {
32
+ if (typeof window === "undefined") return void 0;
33
+ return window.__SSR_DATA__;
34
+ }
35
+ var RiktaProvider = ({
36
+ ssrData: initialSsrData,
37
+ initialParams = {},
38
+ children
39
+ }) => {
40
+ const [ssrData] = useState(() => {
41
+ return initialSsrData ?? getSsrData() ?? null;
42
+ });
43
+ const [location, setLocation] = useState(getLocationInfo);
44
+ const [params, setParams] = useState(initialParams);
45
+ const navigate = useCallback((url, options = {}) => {
46
+ const { replace = false, scroll = true, state } = options;
47
+ if (typeof window === "undefined") return;
48
+ let targetUrl;
49
+ try {
50
+ targetUrl = new URL(url, window.location.origin);
51
+ } catch {
52
+ console.error(`[RiktaReact] Invalid URL: ${url}`);
53
+ return;
54
+ }
55
+ if (targetUrl.origin !== window.location.origin) {
56
+ window.location.href = url;
57
+ return;
58
+ }
59
+ if (replace) {
60
+ window.history.replaceState(state ?? null, "", targetUrl.href);
61
+ } else {
62
+ window.history.pushState(state ?? null, "", targetUrl.href);
63
+ }
64
+ setLocation({
65
+ pathname: targetUrl.pathname,
66
+ search: targetUrl.search.slice(1),
67
+ href: targetUrl.href
68
+ });
69
+ if (scroll) {
70
+ window.scrollTo(0, 0);
71
+ }
72
+ window.dispatchEvent(new PopStateEvent("popstate", { state }));
73
+ }, []);
74
+ useEffect(() => {
75
+ if (typeof window === "undefined") return;
76
+ const handlePopState = () => {
77
+ setLocation(getLocationInfo());
78
+ };
79
+ window.addEventListener("popstate", handlePopState);
80
+ return () => window.removeEventListener("popstate", handlePopState);
81
+ }, []);
82
+ const routerValue = useMemo(() => ({
83
+ pathname: location.pathname,
84
+ search: location.search,
85
+ href: location.href,
86
+ navigate,
87
+ params,
88
+ setParams
89
+ }), [location.pathname, location.search, location.href, navigate, params]);
90
+ return /* @__PURE__ */ jsx(SsrContext.Provider, { value: ssrData, children: /* @__PURE__ */ jsx(RouterContext.Provider, { value: routerValue, children }) });
91
+ };
92
+ RiktaProvider.displayName = "RiktaProvider";
93
+ function useNavigation() {
94
+ const context = useContext(RouterContext);
95
+ const navigate = useCallback(
96
+ (url, options) => {
97
+ context.navigate(url, options);
98
+ },
99
+ [context.navigate]
100
+ );
101
+ return {
102
+ /** Navigate to a new URL */
103
+ navigate,
104
+ /** Current pathname */
105
+ pathname: context.pathname,
106
+ /** Current search string (without ?) */
107
+ search: context.search,
108
+ /** Full href */
109
+ href: context.href
110
+ };
111
+ }
112
+ var Link = ({
113
+ href,
114
+ replace = false,
115
+ scroll = true,
116
+ prefetch = false,
117
+ state,
118
+ children,
119
+ onClick,
120
+ ...restProps
121
+ }) => {
122
+ const { navigate } = useNavigation();
123
+ const handleClick = useCallback(
124
+ (e) => {
125
+ onClick?.(e);
126
+ if (e.defaultPrevented) return;
127
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
128
+ if (e.button !== 0) return;
129
+ const target = e.currentTarget.target;
130
+ if (target && target !== "_self") return;
131
+ try {
132
+ const url = new URL(href, window.location.origin);
133
+ if (url.origin !== window.location.origin) return;
134
+ } catch {
135
+ return;
136
+ }
137
+ e.preventDefault();
138
+ navigate(href, { replace, scroll, state });
139
+ },
140
+ [href, replace, scroll, state, navigate, onClick]
141
+ );
142
+ return /* @__PURE__ */ jsx("a", { href, onClick: handleClick, ...restProps, children });
143
+ };
144
+ Link.displayName = "Link";
145
+ function useParams() {
146
+ const context = useContext(RouterContext);
147
+ return context.params;
148
+ }
149
+ function useSearchParams() {
150
+ const context = useContext(RouterContext);
151
+ const searchParams = useMemo(() => {
152
+ return new URLSearchParams(context.search);
153
+ }, [context.search]);
154
+ const setSearchParams = useMemo(() => {
155
+ return (params) => {
156
+ const newParams = params instanceof URLSearchParams ? params : new URLSearchParams(params);
157
+ const search = newParams.toString();
158
+ const newUrl = search ? `${context.pathname}?${search}` : context.pathname;
159
+ context.navigate(newUrl, { scroll: false });
160
+ };
161
+ }, [context.pathname, context.navigate]);
162
+ return [searchParams, setSearchParams];
163
+ }
164
+ function useLocation() {
165
+ const context = useContext(RouterContext);
166
+ return {
167
+ pathname: context.pathname,
168
+ search: context.search,
169
+ href: context.href,
170
+ searchParams: new URLSearchParams(context.search)
171
+ };
172
+ }
173
+ function useSsrData() {
174
+ const context = useContext(SsrContext);
175
+ return context;
176
+ }
177
+ function useHydration() {
178
+ const isServer = typeof window === "undefined";
179
+ const [isHydrated, setIsHydrated] = useState(false);
180
+ useEffect(() => {
181
+ setIsHydrated(true);
182
+ }, []);
183
+ return {
184
+ isHydrated,
185
+ isServer
186
+ };
187
+ }
188
+ function useFetch(url, options = {}) {
189
+ const { skip = false, deps = [], transform, ...fetchOptions } = options;
190
+ const [state, setState] = useState({
191
+ data: null,
192
+ loading: !skip,
193
+ error: null
194
+ });
195
+ const mountedRef = useRef(true);
196
+ const fetchIdRef = useRef(0);
197
+ const fetchData = useCallback(async () => {
198
+ if (skip) return;
199
+ const fetchId = ++fetchIdRef.current;
200
+ setState((prev) => ({ ...prev, loading: true, error: null }));
201
+ try {
202
+ const response = await fetch(url, fetchOptions);
203
+ if (!response.ok) {
204
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
205
+ }
206
+ let data = await response.json();
207
+ if (transform) {
208
+ data = transform(data);
209
+ }
210
+ if (fetchId === fetchIdRef.current && mountedRef.current) {
211
+ setState({ data, loading: false, error: null });
212
+ }
213
+ } catch (err) {
214
+ if (fetchId === fetchIdRef.current && mountedRef.current) {
215
+ const message = err instanceof Error ? err.message : "An error occurred";
216
+ setState({ data: null, loading: false, error: message });
217
+ }
218
+ }
219
+ }, [url, skip, JSON.stringify(fetchOptions), transform]);
220
+ useEffect(() => {
221
+ mountedRef.current = true;
222
+ fetchData();
223
+ return () => {
224
+ mountedRef.current = false;
225
+ };
226
+ }, [fetchData, ...deps]);
227
+ const refetch = useCallback(async () => {
228
+ await fetchData();
229
+ }, [fetchData]);
230
+ return {
231
+ ...state,
232
+ refetch
233
+ };
234
+ }
235
+ function useAction(url, options = {}) {
236
+ const {
237
+ onSuccess,
238
+ onError,
239
+ method = "POST",
240
+ headers: customHeaders = {}
241
+ } = options;
242
+ const [pending, setPending] = useState(false);
243
+ const [result, setResult] = useState(null);
244
+ const execute = useCallback(
245
+ async (input) => {
246
+ setPending(true);
247
+ setResult(null);
248
+ try {
249
+ const response = await fetch(url, {
250
+ method,
251
+ headers: {
252
+ "Content-Type": "application/json",
253
+ ...customHeaders
254
+ },
255
+ body: JSON.stringify(input)
256
+ });
257
+ const data = await response.json();
258
+ if (!response.ok) {
259
+ const actionResult2 = {
260
+ success: false,
261
+ error: data.message || data.error || `HTTP ${response.status}`,
262
+ fieldErrors: data.fieldErrors
263
+ };
264
+ setResult(actionResult2);
265
+ onError?.(actionResult2.error);
266
+ return actionResult2;
267
+ }
268
+ const actionResult = {
269
+ success: true,
270
+ data
271
+ };
272
+ setResult(actionResult);
273
+ onSuccess?.(data);
274
+ return actionResult;
275
+ } catch (err) {
276
+ const message = err instanceof Error ? err.message : "An error occurred";
277
+ const actionResult = {
278
+ success: false,
279
+ error: message
280
+ };
281
+ setResult(actionResult);
282
+ onError?.(message);
283
+ return actionResult;
284
+ } finally {
285
+ setPending(false);
286
+ }
287
+ },
288
+ [url, method, JSON.stringify(customHeaders), onSuccess, onError]
289
+ );
290
+ const reset = useCallback(() => {
291
+ setResult(null);
292
+ }, []);
293
+ return {
294
+ execute,
295
+ pending,
296
+ result,
297
+ reset
298
+ };
299
+ }
300
+
301
+ export { Link, RiktaProvider, RouterContext, SsrContext, useAction, useFetch, useHydration, useLocation, useNavigation, useParams, useSearchParams, useSsrData };
302
+ //# sourceMappingURL=index.js.map
303
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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};\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() {\n if (typeof window === 'undefined') {\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 */\nfunction getSsrData(): SsrData | undefined {\n if (typeof window === 'undefined') return undefined;\n return window.__SSR_DATA__;\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 * \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] = useState<SsrData | null>(() => {\n return initialSsrData ?? getSsrData() ?? null;\n });\n\n // Initialize location state\n const [location, setLocation] = useState(getLocationInfo);\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\n */\n const navigate = useCallback((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 // 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\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 // Dispatch popstate event for any other listeners\n window.dispatchEvent(new PopStateEvent('popstate', { state }));\n }, []);\n\n // Listen for popstate (browser back/forward)\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handlePopState = () => {\n setLocation(getLocationInfo());\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 }), [location.pathname, location.search, location.href, navigate, params]);\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 fetchData = useCallback(async () => {\n if (skip) 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 fetchData();\n\n return () => {\n mountedRef.current = false;\n };\n }, [fetchData, ...deps]);\n\n const refetch = useCallback(async () => {\n await fetchData();\n }, [fetchData]);\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 ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@riktajs/react",
3
+ "version": "0.10.3",
4
+ "description": "React utilities for Rikta SSR framework - hooks and components for routing, data fetching, and server interaction",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "prebuild": "npm run clean",
28
+ "clean": "rm -rf dist",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "test:coverage": "vitest run --coverage",
32
+ "type-check": "tsc --noEmit"
33
+ },
34
+ "keywords": [
35
+ "rikta",
36
+ "react",
37
+ "ssr",
38
+ "hooks",
39
+ "routing",
40
+ "navigation",
41
+ "fullstack",
42
+ "typescript"
43
+ ],
44
+ "author": "Rikta Team",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/riktar/rikta.git",
49
+ "directory": "packages/react"
50
+ },
51
+ "peerDependencies": {
52
+ "react": "^18.0.0 || ^19.0.0",
53
+ "react-dom": "^18.0.0 || ^19.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@testing-library/jest-dom": "^6.6.3",
57
+ "@testing-library/react": "^16.2.0",
58
+ "@types/node": "^22.10.2",
59
+ "@types/react": "^19.1.0",
60
+ "@types/react-dom": "^19.1.0",
61
+ "@vitest/coverage-v8": "^3.1.1",
62
+ "jsdom": "^26.0.0",
63
+ "react": "^19.1.0",
64
+ "react-dom": "^19.1.0",
65
+ "tsup": "^8.5.1",
66
+ "typescript": "^5.7.2",
67
+ "vitest": "^3.1.1"
68
+ },
69
+ "engines": {
70
+ "node": ">=20.0.0"
71
+ }
72
+ }