@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.
@@ -0,0 +1,609 @@
1
+ import * as react from 'react';
2
+ import { FC } from 'react';
3
+
4
+ /**
5
+ * SSR data structure passed from server to client
6
+ * via window.__SSR_DATA__
7
+ */
8
+ interface SsrData<T = unknown> {
9
+ /** Initial data rendered on server */
10
+ data: T;
11
+ /** Current URL path */
12
+ url: string;
13
+ /** HTTP status code */
14
+ status?: number;
15
+ /** Additional metadata */
16
+ meta?: Record<string, unknown>;
17
+ }
18
+ /**
19
+ * Router context value interface
20
+ */
21
+ interface RouterContextValue {
22
+ /** Current URL path */
23
+ pathname: string;
24
+ /** Current search params string (without ?) */
25
+ search: string;
26
+ /** Full URL */
27
+ href: string;
28
+ /** Navigate to a new URL */
29
+ navigate: (url: string, options?: NavigateOptions) => void;
30
+ /** Extracted route params (e.g., { id: '123' } for /item/:id) */
31
+ params: Record<string, string>;
32
+ /** Update route params (used internally by RiktaProvider) */
33
+ setParams: (params: Record<string, string>) => void;
34
+ }
35
+ /**
36
+ * Navigation options
37
+ */
38
+ interface NavigateOptions {
39
+ /** Replace current history entry instead of pushing */
40
+ replace?: boolean;
41
+ /** Scroll to top after navigation */
42
+ scroll?: boolean;
43
+ /** Additional state to store in history */
44
+ state?: unknown;
45
+ }
46
+ /**
47
+ * Result type for server actions
48
+ */
49
+ interface ActionResult<T = unknown> {
50
+ /** Whether the action was successful */
51
+ success: boolean;
52
+ /** Response data on success */
53
+ data?: T;
54
+ /** Error message on failure */
55
+ error?: string;
56
+ /** Field-specific validation errors */
57
+ fieldErrors?: Record<string, string[]>;
58
+ }
59
+ /**
60
+ * Fetch state for useFetch hook
61
+ */
62
+ interface FetchState<T = unknown> {
63
+ /** Fetched data */
64
+ data: T | null;
65
+ /** Whether fetch is in progress */
66
+ loading: boolean;
67
+ /** Error message if fetch failed */
68
+ error: string | null;
69
+ /** Manually refetch data */
70
+ refetch: () => Promise<void>;
71
+ }
72
+ /**
73
+ * Action state for useAction hook
74
+ */
75
+ interface ActionState<TInput = unknown, TResult = unknown> {
76
+ /** Execute the action */
77
+ execute: (input: TInput) => Promise<ActionResult<TResult>>;
78
+ /** Whether action is executing */
79
+ pending: boolean;
80
+ /** Last action result */
81
+ result: ActionResult<TResult> | null;
82
+ /** Reset action state */
83
+ reset: () => void;
84
+ }
85
+ /**
86
+ * Link component props
87
+ */
88
+ interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
89
+ /** Target URL */
90
+ href: string;
91
+ /** Replace history entry instead of push */
92
+ replace?: boolean;
93
+ /** Scroll to top after navigation */
94
+ scroll?: boolean;
95
+ /** Prefetch the linked page (future enhancement) */
96
+ prefetch?: boolean;
97
+ /** Additional state to pass to navigation */
98
+ state?: unknown;
99
+ /** Children elements */
100
+ children: React.ReactNode;
101
+ }
102
+ /**
103
+ * RiktaProvider props
104
+ */
105
+ interface RiktaProviderProps {
106
+ /** Initial SSR data from server */
107
+ ssrData?: SsrData;
108
+ /** Initial route params extracted from URL */
109
+ initialParams?: Record<string, string>;
110
+ /** Children elements */
111
+ children: React.ReactNode;
112
+ }
113
+ /**
114
+ * Hydration state
115
+ */
116
+ interface HydrationState {
117
+ /** Whether the app has hydrated on client */
118
+ isHydrated: boolean;
119
+ /** Whether currently running on server */
120
+ isServer: boolean;
121
+ }
122
+ declare global {
123
+ interface Window {
124
+ __SSR_DATA__?: SsrData;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * React context for router state
130
+ * Provides navigation utilities and current location info
131
+ */
132
+ declare const RouterContext: react.Context<RouterContextValue>;
133
+
134
+ /**
135
+ * React context for SSR data
136
+ * Holds the server-rendered data passed via window.__SSR_DATA__
137
+ */
138
+ declare const SsrContext: react.Context<SsrData<unknown> | null>;
139
+
140
+ /**
141
+ * RiktaProvider - Main provider component for Rikta React utilities
142
+ *
143
+ * Provides routing context, SSR data, and navigation utilities to the app.
144
+ *
145
+ * @example
146
+ * ```tsx
147
+ * // In entry-client.tsx
148
+ * import { RiktaProvider } from '@riktajs/react';
149
+ *
150
+ * hydrateRoot(
151
+ * document.getElementById('root')!,
152
+ * <RiktaProvider>
153
+ * <App />
154
+ * </RiktaProvider>
155
+ * );
156
+ * ```
157
+ */
158
+ declare const RiktaProvider: FC<RiktaProviderProps>;
159
+
160
+ /**
161
+ * Link component for client-side navigation
162
+ *
163
+ * Renders an anchor tag that uses the History API for navigation
164
+ * instead of causing a full page reload.
165
+ *
166
+ * @example
167
+ * ```tsx
168
+ * import { Link } from '@riktajs/react';
169
+ *
170
+ * function Nav() {
171
+ * return (
172
+ * <nav>
173
+ * <Link href="/">Home</Link>
174
+ * <Link href="/about">About</Link>
175
+ * <Link href="/items/123">Item 123</Link>
176
+ * </nav>
177
+ * );
178
+ * }
179
+ * ```
180
+ *
181
+ * @example
182
+ * ```tsx
183
+ * // With options
184
+ * <Link href="/dashboard" replace scroll={false}>
185
+ * Dashboard
186
+ * </Link>
187
+ * ```
188
+ */
189
+ declare const Link: FC<LinkProps>;
190
+
191
+ /**
192
+ * Hook for programmatic navigation
193
+ *
194
+ * @returns Object with navigate function and current location info
195
+ *
196
+ * @example
197
+ * ```tsx
198
+ * import { useNavigation } from '@riktajs/react';
199
+ *
200
+ * function MyComponent() {
201
+ * const { navigate, pathname } = useNavigation();
202
+ *
203
+ * const handleSubmit = async () => {
204
+ * await saveData();
205
+ * navigate('/success');
206
+ * };
207
+ *
208
+ * return (
209
+ * <button onClick={handleSubmit}>
210
+ * Submit (current path: {pathname})
211
+ * </button>
212
+ * );
213
+ * }
214
+ * ```
215
+ *
216
+ * @example
217
+ * ```tsx
218
+ * // With options
219
+ * const { navigate } = useNavigation();
220
+ *
221
+ * // Replace history entry (for redirects)
222
+ * navigate('/login', { replace: true });
223
+ *
224
+ * // Don't scroll to top
225
+ * navigate('/next', { scroll: false });
226
+ *
227
+ * // Pass state
228
+ * navigate('/edit', { state: { from: 'list' } });
229
+ * ```
230
+ */
231
+ declare function useNavigation(): {
232
+ /** Navigate to a new URL */
233
+ navigate: (url: string, options?: NavigateOptions) => void;
234
+ /** Current pathname */
235
+ pathname: string;
236
+ /** Current search string (without ?) */
237
+ search: string;
238
+ /** Full href */
239
+ href: string;
240
+ };
241
+
242
+ /**
243
+ * Hook to access route parameters
244
+ *
245
+ * Route parameters are extracted from the URL path by the server
246
+ * and passed via SSR data. They're stored in the RouterContext.
247
+ *
248
+ * @returns Object with route parameter values
249
+ *
250
+ * @example
251
+ * ```tsx
252
+ * // For route /item/:id
253
+ * import { useParams } from '@riktajs/react';
254
+ *
255
+ * function ItemPage() {
256
+ * const { id } = useParams<{ id: string }>();
257
+ *
258
+ * return <h1>Item {id}</h1>;
259
+ * }
260
+ * ```
261
+ *
262
+ * @example
263
+ * ```tsx
264
+ * // Multiple params - /users/:userId/posts/:postId
265
+ * function PostPage() {
266
+ * const { userId, postId } = useParams<{ userId: string; postId: string }>();
267
+ *
268
+ * return <h1>Post {postId} by User {userId}</h1>;
269
+ * }
270
+ * ```
271
+ */
272
+ declare function useParams<T extends Record<string, string> = Record<string, string>>(): T;
273
+
274
+ /**
275
+ * Hook to access and manipulate URL search parameters
276
+ *
277
+ * @returns Tuple of [URLSearchParams, setSearchParams function]
278
+ *
279
+ * @example
280
+ * ```tsx
281
+ * import { useSearchParams } from '@riktajs/react';
282
+ *
283
+ * function SearchPage() {
284
+ * const [searchParams, setSearchParams] = useSearchParams();
285
+ * const query = searchParams.get('q') ?? '';
286
+ * const page = parseInt(searchParams.get('page') ?? '1', 10);
287
+ *
288
+ * const handleSearch = (newQuery: string) => {
289
+ * setSearchParams({ q: newQuery, page: '1' });
290
+ * };
291
+ *
292
+ * const handleNextPage = () => {
293
+ * setSearchParams({ q: query, page: String(page + 1) });
294
+ * };
295
+ *
296
+ * return (
297
+ * <div>
298
+ * <input
299
+ * value={query}
300
+ * onChange={(e) => handleSearch(e.target.value)}
301
+ * />
302
+ * <button onClick={handleNextPage}>Next Page</button>
303
+ * </div>
304
+ * );
305
+ * }
306
+ * ```
307
+ */
308
+ declare function useSearchParams(): [URLSearchParams, (params: Record<string, string> | URLSearchParams) => void];
309
+
310
+ /**
311
+ * Location object returned by useLocation
312
+ */
313
+ interface Location {
314
+ /** Current pathname (e.g., /items/123) */
315
+ pathname: string;
316
+ /** Current search string without ? (e.g., page=2&sort=asc) */
317
+ search: string;
318
+ /** Full href */
319
+ href: string;
320
+ /** Parsed search params */
321
+ searchParams: URLSearchParams;
322
+ }
323
+ /**
324
+ * Hook to access current location information
325
+ *
326
+ * @returns Location object with pathname, search, href, and searchParams
327
+ *
328
+ * @example
329
+ * ```tsx
330
+ * import { useLocation } from '@riktajs/react';
331
+ *
332
+ * function Breadcrumbs() {
333
+ * const location = useLocation();
334
+ *
335
+ * return (
336
+ * <nav>
337
+ * Current path: {location.pathname}
338
+ * {location.search && <span>?{location.search}</span>}
339
+ * </nav>
340
+ * );
341
+ * }
342
+ * ```
343
+ *
344
+ * @example
345
+ * ```tsx
346
+ * // Access search params
347
+ * function FilterDisplay() {
348
+ * const { searchParams } = useLocation();
349
+ * const filter = searchParams.get('filter');
350
+ *
351
+ * return filter ? <span>Filtered by: {filter}</span> : null;
352
+ * }
353
+ * ```
354
+ */
355
+ declare function useLocation(): Location;
356
+
357
+ /**
358
+ * Hook to access SSR data passed from server
359
+ *
360
+ * SSR data is passed via window.__SSR_DATA__ and contains
361
+ * the initial data rendered on the server.
362
+ *
363
+ * @returns SSR data object or null if not available
364
+ *
365
+ * @example
366
+ * ```tsx
367
+ * import { useSsrData } from '@riktajs/react';
368
+ *
369
+ * interface PageData {
370
+ * title: string;
371
+ * items: Array<{ id: string; name: string }>;
372
+ * }
373
+ *
374
+ * function ItemList() {
375
+ * const ssrData = useSsrData<PageData>();
376
+ *
377
+ * if (!ssrData) {
378
+ * return <div>Loading...</div>;
379
+ * }
380
+ *
381
+ * return (
382
+ * <div>
383
+ * <h1>{ssrData.data.title}</h1>
384
+ * <ul>
385
+ * {ssrData.data.items.map(item => (
386
+ * <li key={item.id}>{item.name}</li>
387
+ * ))}
388
+ * </ul>
389
+ * </div>
390
+ * );
391
+ * }
392
+ * ```
393
+ *
394
+ * @example
395
+ * ```tsx
396
+ * // Access just the data
397
+ * function MyComponent() {
398
+ * const ssrData = useSsrData<{ user: User }>();
399
+ * const user = ssrData?.data.user;
400
+ *
401
+ * return user ? <UserProfile user={user} /> : <LoginPrompt />;
402
+ * }
403
+ * ```
404
+ */
405
+ declare function useSsrData<T = unknown>(): SsrData<T> | null;
406
+
407
+ /**
408
+ * Hook to track hydration state
409
+ *
410
+ * Useful for rendering different content during SSR vs after hydration,
411
+ * or for avoiding hydration mismatches.
412
+ *
413
+ * @returns Hydration state object
414
+ *
415
+ * @example
416
+ * ```tsx
417
+ * import { useHydration } from '@riktajs/react';
418
+ *
419
+ * function TimeDisplay() {
420
+ * const { isHydrated, isServer } = useHydration();
421
+ *
422
+ * // On server and initial render, show static content
423
+ * // After hydration, show dynamic content
424
+ * if (!isHydrated) {
425
+ * return <span>Loading time...</span>;
426
+ * }
427
+ *
428
+ * return <span>{new Date().toLocaleTimeString()}</span>;
429
+ * }
430
+ * ```
431
+ *
432
+ * @example
433
+ * ```tsx
434
+ * // Avoid hydration mismatch with client-only content
435
+ * function ClientOnlyComponent() {
436
+ * const { isHydrated } = useHydration();
437
+ *
438
+ * if (!isHydrated) {
439
+ * return null; // Or a placeholder
440
+ * }
441
+ *
442
+ * return <SomeClientOnlyLibrary />;
443
+ * }
444
+ * ```
445
+ *
446
+ * @example
447
+ * ```tsx
448
+ * // Conditional rendering based on environment
449
+ * function DebugPanel() {
450
+ * const { isServer } = useHydration();
451
+ *
452
+ * // Never render on server, only after client hydration
453
+ * if (isServer) return null;
454
+ *
455
+ * return <DevTools />;
456
+ * }
457
+ * ```
458
+ */
459
+ declare function useHydration(): HydrationState;
460
+
461
+ /**
462
+ * Options for useFetch hook
463
+ */
464
+ interface UseFetchOptions extends Omit<RequestInit, 'body'> {
465
+ /** Skip initial fetch (useful for conditional fetching) */
466
+ skip?: boolean;
467
+ /** Dependencies that trigger refetch when changed */
468
+ deps?: unknown[];
469
+ /** Transform response before setting data */
470
+ transform?: (data: unknown) => unknown;
471
+ }
472
+ /**
473
+ * Hook for data fetching with loading and error states
474
+ *
475
+ * @param url URL to fetch from
476
+ * @param options Fetch options
477
+ * @returns Fetch state with data, loading, error, and refetch function
478
+ *
479
+ * @example
480
+ * ```tsx
481
+ * import { useFetch } from '@riktajs/react';
482
+ *
483
+ * interface User {
484
+ * id: string;
485
+ * name: string;
486
+ * }
487
+ *
488
+ * function UserProfile({ userId }: { userId: string }) {
489
+ * const { data, loading, error, refetch } = useFetch<User>(
490
+ * `/api/users/${userId}`
491
+ * );
492
+ *
493
+ * if (loading) return <Spinner />;
494
+ * if (error) return <Error message={error} />;
495
+ * if (!data) return null;
496
+ *
497
+ * return (
498
+ * <div>
499
+ * <h1>{data.name}</h1>
500
+ * <button onClick={refetch}>Refresh</button>
501
+ * </div>
502
+ * );
503
+ * }
504
+ * ```
505
+ *
506
+ * @example
507
+ * ```tsx
508
+ * // With options
509
+ * const { data } = useFetch<Item[]>('/api/items', {
510
+ * headers: { 'Authorization': `Bearer ${token}` },
511
+ * deps: [token], // Refetch when token changes
512
+ * skip: !token, // Don't fetch until we have a token
513
+ * });
514
+ * ```
515
+ *
516
+ * @example
517
+ * ```tsx
518
+ * // With transform
519
+ * const { data } = useFetch<{ results: Item[] }>('/api/search', {
520
+ * transform: (res) => res.results, // Extract just the results array
521
+ * });
522
+ * ```
523
+ */
524
+ declare function useFetch<T = unknown>(url: string, options?: UseFetchOptions): FetchState<T>;
525
+
526
+ /**
527
+ * Options for useAction hook
528
+ */
529
+ interface UseActionOptions<TResult = unknown> {
530
+ /** Callback on successful action */
531
+ onSuccess?: (result: TResult) => void;
532
+ /** Callback on action error */
533
+ onError?: (error: string) => void;
534
+ /** HTTP method to use */
535
+ method?: 'POST' | 'PUT' | 'PATCH' | 'DELETE';
536
+ /** Additional headers */
537
+ headers?: Record<string, string>;
538
+ }
539
+ /**
540
+ * Hook for executing server actions (form submissions, mutations)
541
+ *
542
+ * @param url URL to send the action to
543
+ * @param options Action options
544
+ * @returns Action state with execute, pending, result, and reset
545
+ *
546
+ * @example
547
+ * ```tsx
548
+ * import { useAction } from '@riktajs/react';
549
+ *
550
+ * interface CreateItemInput {
551
+ * name: string;
552
+ * price: number;
553
+ * }
554
+ *
555
+ * interface Item {
556
+ * id: string;
557
+ * name: string;
558
+ * price: number;
559
+ * }
560
+ *
561
+ * function CreateItemForm() {
562
+ * const { execute, pending, result } = useAction<CreateItemInput, Item>(
563
+ * '/api/items',
564
+ * {
565
+ * onSuccess: (item) => {
566
+ * console.log('Created item:', item);
567
+ * },
568
+ * }
569
+ * );
570
+ *
571
+ * const handleSubmit = async (e: FormEvent) => {
572
+ * e.preventDefault();
573
+ * const formData = new FormData(e.target as HTMLFormElement);
574
+ * await execute({
575
+ * name: formData.get('name') as string,
576
+ * price: Number(formData.get('price')),
577
+ * });
578
+ * };
579
+ *
580
+ * return (
581
+ * <form onSubmit={handleSubmit}>
582
+ * <input name="name" required />
583
+ * <input name="price" type="number" required />
584
+ * <button disabled={pending}>
585
+ * {pending ? 'Creating...' : 'Create Item'}
586
+ * </button>
587
+ * {result?.error && <p className="error">{result.error}</p>}
588
+ * {result?.fieldErrors?.name && (
589
+ * <p className="error">{result.fieldErrors.name[0]}</p>
590
+ * )}
591
+ * </form>
592
+ * );
593
+ * }
594
+ * ```
595
+ *
596
+ * @example
597
+ * ```tsx
598
+ * // DELETE action
599
+ * const { execute, pending } = useAction<{ id: string }, void>(
600
+ * '/api/items',
601
+ * { method: 'DELETE' }
602
+ * );
603
+ *
604
+ * const handleDelete = () => execute({ id: itemId });
605
+ * ```
606
+ */
607
+ declare function useAction<TInput = unknown, TResult = unknown>(url: string, options?: UseActionOptions<TResult>): ActionState<TInput, TResult>;
608
+
609
+ export { type ActionResult, type ActionState, type FetchState, type HydrationState, Link, type LinkProps, type Location, type NavigateOptions, RiktaProvider, type RiktaProviderProps, RouterContext, type RouterContextValue, SsrContext, type SsrData, type UseActionOptions, type UseFetchOptions, useAction, useFetch, useHydration, useLocation, useNavigation, useParams, useSearchParams, useSsrData };