@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/README.md +368 -0
- package/dist/index.cjs +316 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +609 -0
- package/dist/index.d.ts +609 -0
- package/dist/index.js +303 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# @riktajs/react
|
|
2
|
+
|
|
3
|
+
React utilities for Rikta SSR framework. Provides hooks and components for routing, navigation, SSR data access, and server interactions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @riktajs/react
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @riktajs/react
|
|
11
|
+
# or
|
|
12
|
+
yarn add @riktajs/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
Wrap your app with `RiktaProvider` to enable all features:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// entry-client.tsx
|
|
21
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
22
|
+
import { RiktaProvider } from '@riktajs/react';
|
|
23
|
+
import App from './App';
|
|
24
|
+
|
|
25
|
+
hydrateRoot(
|
|
26
|
+
document.getElementById('root')!,
|
|
27
|
+
<RiktaProvider>
|
|
28
|
+
<App />
|
|
29
|
+
</RiktaProvider>
|
|
30
|
+
);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Components
|
|
34
|
+
|
|
35
|
+
### `<Link>`
|
|
36
|
+
|
|
37
|
+
Client-side navigation link component that uses the History API instead of full page reloads.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Link } from '@riktajs/react';
|
|
41
|
+
|
|
42
|
+
function Navigation() {
|
|
43
|
+
return (
|
|
44
|
+
<nav>
|
|
45
|
+
<Link href="/">Home</Link>
|
|
46
|
+
<Link href="/about">About</Link>
|
|
47
|
+
<Link href="/items/123">Item 123</Link>
|
|
48
|
+
|
|
49
|
+
{/* With options */}
|
|
50
|
+
<Link href="/dashboard" replace scroll={false}>
|
|
51
|
+
Dashboard
|
|
52
|
+
</Link>
|
|
53
|
+
</nav>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Props:**
|
|
59
|
+
- `href` (required): Target URL
|
|
60
|
+
- `replace`: Replace history entry instead of push
|
|
61
|
+
- `scroll`: Scroll to top after navigation (default: `true`)
|
|
62
|
+
- `state`: Additional state to pass to navigation
|
|
63
|
+
- All standard `<a>` props are supported
|
|
64
|
+
|
|
65
|
+
### `<RiktaProvider>`
|
|
66
|
+
|
|
67
|
+
Root provider component that enables all Rikta React utilities.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { RiktaProvider } from '@riktajs/react';
|
|
71
|
+
|
|
72
|
+
function App() {
|
|
73
|
+
return (
|
|
74
|
+
<RiktaProvider>
|
|
75
|
+
<YourApp />
|
|
76
|
+
</RiktaProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Hooks
|
|
82
|
+
|
|
83
|
+
### `useNavigation()`
|
|
84
|
+
|
|
85
|
+
Programmatic navigation hook.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { useNavigation } from '@riktajs/react';
|
|
89
|
+
|
|
90
|
+
function MyComponent() {
|
|
91
|
+
const { navigate, pathname } = useNavigation();
|
|
92
|
+
|
|
93
|
+
const handleSubmit = async () => {
|
|
94
|
+
await saveData();
|
|
95
|
+
navigate('/success');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// With options
|
|
99
|
+
navigate('/login', { replace: true });
|
|
100
|
+
navigate('/next', { scroll: false });
|
|
101
|
+
navigate('/edit', { state: { from: 'list' } });
|
|
102
|
+
|
|
103
|
+
return <button onClick={handleSubmit}>Submit</button>;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `useParams()`
|
|
108
|
+
|
|
109
|
+
Access route parameters extracted from dynamic URLs.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useParams } from '@riktajs/react';
|
|
113
|
+
|
|
114
|
+
// For route /item/:id
|
|
115
|
+
function ItemPage() {
|
|
116
|
+
const { id } = useParams<{ id: string }>();
|
|
117
|
+
|
|
118
|
+
return <h1>Item {id}</h1>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Multiple params - /users/:userId/posts/:postId
|
|
122
|
+
function PostPage() {
|
|
123
|
+
const { userId, postId } = useParams<{ userId: string; postId: string }>();
|
|
124
|
+
|
|
125
|
+
return <h1>Post {postId} by User {userId}</h1>;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `useSearchParams()`
|
|
130
|
+
|
|
131
|
+
Access and update URL search parameters.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { useSearchParams } from '@riktajs/react';
|
|
135
|
+
|
|
136
|
+
function SearchPage() {
|
|
137
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
138
|
+
const query = searchParams.get('q') ?? '';
|
|
139
|
+
const page = parseInt(searchParams.get('page') ?? '1', 10);
|
|
140
|
+
|
|
141
|
+
const handleSearch = (newQuery: string) => {
|
|
142
|
+
setSearchParams({ q: newQuery, page: '1' });
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<input
|
|
147
|
+
value={query}
|
|
148
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `useLocation()`
|
|
155
|
+
|
|
156
|
+
Get current location information.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { useLocation } from '@riktajs/react';
|
|
160
|
+
|
|
161
|
+
function Breadcrumbs() {
|
|
162
|
+
const { pathname, search, searchParams } = useLocation();
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<nav>
|
|
166
|
+
Current path: {pathname}
|
|
167
|
+
{search && <span>?{search}</span>}
|
|
168
|
+
</nav>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `useSsrData()`
|
|
174
|
+
|
|
175
|
+
Access SSR data passed from server via `window.__SSR_DATA__`.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { useSsrData } from '@riktajs/react';
|
|
179
|
+
|
|
180
|
+
interface PageData {
|
|
181
|
+
title: string;
|
|
182
|
+
items: Array<{ id: string; name: string }>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function ItemList() {
|
|
186
|
+
const ssrData = useSsrData<PageData>();
|
|
187
|
+
|
|
188
|
+
if (!ssrData) return <div>Loading...</div>;
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div>
|
|
192
|
+
<h1>{ssrData.data.title}</h1>
|
|
193
|
+
<ul>
|
|
194
|
+
{ssrData.data.items.map(item => (
|
|
195
|
+
<li key={item.id}>{item.name}</li>
|
|
196
|
+
))}
|
|
197
|
+
</ul>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### `useHydration()`
|
|
204
|
+
|
|
205
|
+
Track hydration state for client-only rendering.
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { useHydration } from '@riktajs/react';
|
|
209
|
+
|
|
210
|
+
function TimeDisplay() {
|
|
211
|
+
const { isHydrated, isServer } = useHydration();
|
|
212
|
+
|
|
213
|
+
// Avoid hydration mismatch with dynamic content
|
|
214
|
+
if (!isHydrated) {
|
|
215
|
+
return <span>Loading time...</span>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return <span>{new Date().toLocaleTimeString()}</span>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function ClientOnlyComponent() {
|
|
222
|
+
const { isHydrated } = useHydration();
|
|
223
|
+
|
|
224
|
+
if (!isHydrated) return null;
|
|
225
|
+
|
|
226
|
+
return <SomeClientOnlyLibrary />;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### `useFetch()`
|
|
231
|
+
|
|
232
|
+
Data fetching hook with loading and error states.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { useFetch } from '@riktajs/react';
|
|
236
|
+
|
|
237
|
+
interface User {
|
|
238
|
+
id: string;
|
|
239
|
+
name: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
243
|
+
const { data, loading, error, refetch } = useFetch<User>(
|
|
244
|
+
`/api/users/${userId}`
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (loading) return <Spinner />;
|
|
248
|
+
if (error) return <Error message={error} />;
|
|
249
|
+
if (!data) return null;
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<div>
|
|
253
|
+
<h1>{data.name}</h1>
|
|
254
|
+
<button onClick={refetch}>Refresh</button>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// With options
|
|
260
|
+
const { data } = useFetch<Item[]>('/api/items', {
|
|
261
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
262
|
+
deps: [token], // Refetch when token changes
|
|
263
|
+
skip: !token, // Don't fetch until we have a token
|
|
264
|
+
transform: (res) => res.results, // Transform response
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `useAction()`
|
|
269
|
+
|
|
270
|
+
Execute server actions (form submissions, mutations).
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { useAction } from '@riktajs/react';
|
|
274
|
+
|
|
275
|
+
interface CreateItemInput {
|
|
276
|
+
name: string;
|
|
277
|
+
price: number;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interface Item {
|
|
281
|
+
id: string;
|
|
282
|
+
name: string;
|
|
283
|
+
price: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function CreateItemForm() {
|
|
287
|
+
const { execute, pending, result } = useAction<CreateItemInput, Item>(
|
|
288
|
+
'/api/items',
|
|
289
|
+
{
|
|
290
|
+
onSuccess: (item) => console.log('Created:', item),
|
|
291
|
+
onError: (error) => console.error('Failed:', error),
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const handleSubmit = async (e: FormEvent) => {
|
|
296
|
+
e.preventDefault();
|
|
297
|
+
const formData = new FormData(e.target as HTMLFormElement);
|
|
298
|
+
await execute({
|
|
299
|
+
name: formData.get('name') as string,
|
|
300
|
+
price: Number(formData.get('price')),
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<form onSubmit={handleSubmit}>
|
|
306
|
+
<input name="name" required />
|
|
307
|
+
<input name="price" type="number" required />
|
|
308
|
+
<button disabled={pending}>
|
|
309
|
+
{pending ? 'Creating...' : 'Create Item'}
|
|
310
|
+
</button>
|
|
311
|
+
{result?.error && <p className="error">{result.error}</p>}
|
|
312
|
+
</form>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// DELETE action
|
|
317
|
+
const { execute, pending } = useAction<{ id: string }, void>(
|
|
318
|
+
'/api/items',
|
|
319
|
+
{ method: 'DELETE' }
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## TypeScript
|
|
324
|
+
|
|
325
|
+
All exports are fully typed. Import types as needed:
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
import type {
|
|
329
|
+
SsrData,
|
|
330
|
+
RouterContextValue,
|
|
331
|
+
NavigateOptions,
|
|
332
|
+
ActionResult,
|
|
333
|
+
FetchState,
|
|
334
|
+
ActionState,
|
|
335
|
+
LinkProps,
|
|
336
|
+
HydrationState,
|
|
337
|
+
Location,
|
|
338
|
+
} from '@riktajs/react';
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Integration with @riktajs/ssr
|
|
342
|
+
|
|
343
|
+
This package is designed to work seamlessly with `@riktajs/ssr`. The SSR plugin automatically injects `window.__SSR_DATA__` which `RiktaProvider` picks up.
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
// Server: page.controller.ts
|
|
347
|
+
@Controller()
|
|
348
|
+
export class PageController {
|
|
349
|
+
@Get('/item/:id')
|
|
350
|
+
@Render()
|
|
351
|
+
getItem(@Param('id') id: string) {
|
|
352
|
+
const item = getItemById(id);
|
|
353
|
+
return { item, params: { id } };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Client: ItemPage.tsx
|
|
358
|
+
function ItemPage() {
|
|
359
|
+
const ssrData = useSsrData<{ item: Item; params: { id: string } }>();
|
|
360
|
+
const { id } = useParams();
|
|
361
|
+
|
|
362
|
+
return <h1>{ssrData?.data.item.name} (ID: {id})</h1>;
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/context/RouterContext.ts
|
|
7
|
+
var defaultRouterContext = {
|
|
8
|
+
pathname: "/",
|
|
9
|
+
search: "",
|
|
10
|
+
href: "/",
|
|
11
|
+
navigate: () => {
|
|
12
|
+
console.warn("[RiktaReact] Router context not initialized. Wrap your app with <RiktaProvider>");
|
|
13
|
+
},
|
|
14
|
+
params: {},
|
|
15
|
+
setParams: () => {
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var RouterContext = react.createContext(defaultRouterContext);
|
|
19
|
+
RouterContext.displayName = "RiktaRouterContext";
|
|
20
|
+
var SsrContext = react.createContext(null);
|
|
21
|
+
SsrContext.displayName = "RiktaSsrContext";
|
|
22
|
+
function getLocationInfo() {
|
|
23
|
+
if (typeof window === "undefined") {
|
|
24
|
+
return { pathname: "/", search: "", href: "/" };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
pathname: window.location.pathname,
|
|
28
|
+
search: window.location.search.slice(1),
|
|
29
|
+
// Remove leading ?
|
|
30
|
+
href: window.location.href
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function getSsrData() {
|
|
34
|
+
if (typeof window === "undefined") return void 0;
|
|
35
|
+
return window.__SSR_DATA__;
|
|
36
|
+
}
|
|
37
|
+
var RiktaProvider = ({
|
|
38
|
+
ssrData: initialSsrData,
|
|
39
|
+
initialParams = {},
|
|
40
|
+
children
|
|
41
|
+
}) => {
|
|
42
|
+
const [ssrData] = react.useState(() => {
|
|
43
|
+
return initialSsrData ?? getSsrData() ?? null;
|
|
44
|
+
});
|
|
45
|
+
const [location, setLocation] = react.useState(getLocationInfo);
|
|
46
|
+
const [params, setParams] = react.useState(initialParams);
|
|
47
|
+
const navigate = react.useCallback((url, options = {}) => {
|
|
48
|
+
const { replace = false, scroll = true, state } = options;
|
|
49
|
+
if (typeof window === "undefined") return;
|
|
50
|
+
let targetUrl;
|
|
51
|
+
try {
|
|
52
|
+
targetUrl = new URL(url, window.location.origin);
|
|
53
|
+
} catch {
|
|
54
|
+
console.error(`[RiktaReact] Invalid URL: ${url}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (targetUrl.origin !== window.location.origin) {
|
|
58
|
+
window.location.href = url;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (replace) {
|
|
62
|
+
window.history.replaceState(state ?? null, "", targetUrl.href);
|
|
63
|
+
} else {
|
|
64
|
+
window.history.pushState(state ?? null, "", targetUrl.href);
|
|
65
|
+
}
|
|
66
|
+
setLocation({
|
|
67
|
+
pathname: targetUrl.pathname,
|
|
68
|
+
search: targetUrl.search.slice(1),
|
|
69
|
+
href: targetUrl.href
|
|
70
|
+
});
|
|
71
|
+
if (scroll) {
|
|
72
|
+
window.scrollTo(0, 0);
|
|
73
|
+
}
|
|
74
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state }));
|
|
75
|
+
}, []);
|
|
76
|
+
react.useEffect(() => {
|
|
77
|
+
if (typeof window === "undefined") return;
|
|
78
|
+
const handlePopState = () => {
|
|
79
|
+
setLocation(getLocationInfo());
|
|
80
|
+
};
|
|
81
|
+
window.addEventListener("popstate", handlePopState);
|
|
82
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
83
|
+
}, []);
|
|
84
|
+
const routerValue = react.useMemo(() => ({
|
|
85
|
+
pathname: location.pathname,
|
|
86
|
+
search: location.search,
|
|
87
|
+
href: location.href,
|
|
88
|
+
navigate,
|
|
89
|
+
params,
|
|
90
|
+
setParams
|
|
91
|
+
}), [location.pathname, location.search, location.href, navigate, params]);
|
|
92
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SsrContext.Provider, { value: ssrData, children: /* @__PURE__ */ jsxRuntime.jsx(RouterContext.Provider, { value: routerValue, children }) });
|
|
93
|
+
};
|
|
94
|
+
RiktaProvider.displayName = "RiktaProvider";
|
|
95
|
+
function useNavigation() {
|
|
96
|
+
const context = react.useContext(RouterContext);
|
|
97
|
+
const navigate = react.useCallback(
|
|
98
|
+
(url, options) => {
|
|
99
|
+
context.navigate(url, options);
|
|
100
|
+
},
|
|
101
|
+
[context.navigate]
|
|
102
|
+
);
|
|
103
|
+
return {
|
|
104
|
+
/** Navigate to a new URL */
|
|
105
|
+
navigate,
|
|
106
|
+
/** Current pathname */
|
|
107
|
+
pathname: context.pathname,
|
|
108
|
+
/** Current search string (without ?) */
|
|
109
|
+
search: context.search,
|
|
110
|
+
/** Full href */
|
|
111
|
+
href: context.href
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
var Link = ({
|
|
115
|
+
href,
|
|
116
|
+
replace = false,
|
|
117
|
+
scroll = true,
|
|
118
|
+
prefetch = false,
|
|
119
|
+
state,
|
|
120
|
+
children,
|
|
121
|
+
onClick,
|
|
122
|
+
...restProps
|
|
123
|
+
}) => {
|
|
124
|
+
const { navigate } = useNavigation();
|
|
125
|
+
const handleClick = react.useCallback(
|
|
126
|
+
(e) => {
|
|
127
|
+
onClick?.(e);
|
|
128
|
+
if (e.defaultPrevented) return;
|
|
129
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
130
|
+
if (e.button !== 0) return;
|
|
131
|
+
const target = e.currentTarget.target;
|
|
132
|
+
if (target && target !== "_self") return;
|
|
133
|
+
try {
|
|
134
|
+
const url = new URL(href, window.location.origin);
|
|
135
|
+
if (url.origin !== window.location.origin) return;
|
|
136
|
+
} catch {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
navigate(href, { replace, scroll, state });
|
|
141
|
+
},
|
|
142
|
+
[href, replace, scroll, state, navigate, onClick]
|
|
143
|
+
);
|
|
144
|
+
return /* @__PURE__ */ jsxRuntime.jsx("a", { href, onClick: handleClick, ...restProps, children });
|
|
145
|
+
};
|
|
146
|
+
Link.displayName = "Link";
|
|
147
|
+
function useParams() {
|
|
148
|
+
const context = react.useContext(RouterContext);
|
|
149
|
+
return context.params;
|
|
150
|
+
}
|
|
151
|
+
function useSearchParams() {
|
|
152
|
+
const context = react.useContext(RouterContext);
|
|
153
|
+
const searchParams = react.useMemo(() => {
|
|
154
|
+
return new URLSearchParams(context.search);
|
|
155
|
+
}, [context.search]);
|
|
156
|
+
const setSearchParams = react.useMemo(() => {
|
|
157
|
+
return (params) => {
|
|
158
|
+
const newParams = params instanceof URLSearchParams ? params : new URLSearchParams(params);
|
|
159
|
+
const search = newParams.toString();
|
|
160
|
+
const newUrl = search ? `${context.pathname}?${search}` : context.pathname;
|
|
161
|
+
context.navigate(newUrl, { scroll: false });
|
|
162
|
+
};
|
|
163
|
+
}, [context.pathname, context.navigate]);
|
|
164
|
+
return [searchParams, setSearchParams];
|
|
165
|
+
}
|
|
166
|
+
function useLocation() {
|
|
167
|
+
const context = react.useContext(RouterContext);
|
|
168
|
+
return {
|
|
169
|
+
pathname: context.pathname,
|
|
170
|
+
search: context.search,
|
|
171
|
+
href: context.href,
|
|
172
|
+
searchParams: new URLSearchParams(context.search)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function useSsrData() {
|
|
176
|
+
const context = react.useContext(SsrContext);
|
|
177
|
+
return context;
|
|
178
|
+
}
|
|
179
|
+
function useHydration() {
|
|
180
|
+
const isServer = typeof window === "undefined";
|
|
181
|
+
const [isHydrated, setIsHydrated] = react.useState(false);
|
|
182
|
+
react.useEffect(() => {
|
|
183
|
+
setIsHydrated(true);
|
|
184
|
+
}, []);
|
|
185
|
+
return {
|
|
186
|
+
isHydrated,
|
|
187
|
+
isServer
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function useFetch(url, options = {}) {
|
|
191
|
+
const { skip = false, deps = [], transform, ...fetchOptions } = options;
|
|
192
|
+
const [state, setState] = react.useState({
|
|
193
|
+
data: null,
|
|
194
|
+
loading: !skip,
|
|
195
|
+
error: null
|
|
196
|
+
});
|
|
197
|
+
const mountedRef = react.useRef(true);
|
|
198
|
+
const fetchIdRef = react.useRef(0);
|
|
199
|
+
const fetchData = react.useCallback(async () => {
|
|
200
|
+
if (skip) return;
|
|
201
|
+
const fetchId = ++fetchIdRef.current;
|
|
202
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
203
|
+
try {
|
|
204
|
+
const response = await fetch(url, fetchOptions);
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
207
|
+
}
|
|
208
|
+
let data = await response.json();
|
|
209
|
+
if (transform) {
|
|
210
|
+
data = transform(data);
|
|
211
|
+
}
|
|
212
|
+
if (fetchId === fetchIdRef.current && mountedRef.current) {
|
|
213
|
+
setState({ data, loading: false, error: null });
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (fetchId === fetchIdRef.current && mountedRef.current) {
|
|
217
|
+
const message = err instanceof Error ? err.message : "An error occurred";
|
|
218
|
+
setState({ data: null, loading: false, error: message });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, [url, skip, JSON.stringify(fetchOptions), transform]);
|
|
222
|
+
react.useEffect(() => {
|
|
223
|
+
mountedRef.current = true;
|
|
224
|
+
fetchData();
|
|
225
|
+
return () => {
|
|
226
|
+
mountedRef.current = false;
|
|
227
|
+
};
|
|
228
|
+
}, [fetchData, ...deps]);
|
|
229
|
+
const refetch = react.useCallback(async () => {
|
|
230
|
+
await fetchData();
|
|
231
|
+
}, [fetchData]);
|
|
232
|
+
return {
|
|
233
|
+
...state,
|
|
234
|
+
refetch
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function useAction(url, options = {}) {
|
|
238
|
+
const {
|
|
239
|
+
onSuccess,
|
|
240
|
+
onError,
|
|
241
|
+
method = "POST",
|
|
242
|
+
headers: customHeaders = {}
|
|
243
|
+
} = options;
|
|
244
|
+
const [pending, setPending] = react.useState(false);
|
|
245
|
+
const [result, setResult] = react.useState(null);
|
|
246
|
+
const execute = react.useCallback(
|
|
247
|
+
async (input) => {
|
|
248
|
+
setPending(true);
|
|
249
|
+
setResult(null);
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch(url, {
|
|
252
|
+
method,
|
|
253
|
+
headers: {
|
|
254
|
+
"Content-Type": "application/json",
|
|
255
|
+
...customHeaders
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify(input)
|
|
258
|
+
});
|
|
259
|
+
const data = await response.json();
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
const actionResult2 = {
|
|
262
|
+
success: false,
|
|
263
|
+
error: data.message || data.error || `HTTP ${response.status}`,
|
|
264
|
+
fieldErrors: data.fieldErrors
|
|
265
|
+
};
|
|
266
|
+
setResult(actionResult2);
|
|
267
|
+
onError?.(actionResult2.error);
|
|
268
|
+
return actionResult2;
|
|
269
|
+
}
|
|
270
|
+
const actionResult = {
|
|
271
|
+
success: true,
|
|
272
|
+
data
|
|
273
|
+
};
|
|
274
|
+
setResult(actionResult);
|
|
275
|
+
onSuccess?.(data);
|
|
276
|
+
return actionResult;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
const message = err instanceof Error ? err.message : "An error occurred";
|
|
279
|
+
const actionResult = {
|
|
280
|
+
success: false,
|
|
281
|
+
error: message
|
|
282
|
+
};
|
|
283
|
+
setResult(actionResult);
|
|
284
|
+
onError?.(message);
|
|
285
|
+
return actionResult;
|
|
286
|
+
} finally {
|
|
287
|
+
setPending(false);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
[url, method, JSON.stringify(customHeaders), onSuccess, onError]
|
|
291
|
+
);
|
|
292
|
+
const reset = react.useCallback(() => {
|
|
293
|
+
setResult(null);
|
|
294
|
+
}, []);
|
|
295
|
+
return {
|
|
296
|
+
execute,
|
|
297
|
+
pending,
|
|
298
|
+
result,
|
|
299
|
+
reset
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
exports.Link = Link;
|
|
304
|
+
exports.RiktaProvider = RiktaProvider;
|
|
305
|
+
exports.RouterContext = RouterContext;
|
|
306
|
+
exports.SsrContext = SsrContext;
|
|
307
|
+
exports.useAction = useAction;
|
|
308
|
+
exports.useFetch = useFetch;
|
|
309
|
+
exports.useHydration = useHydration;
|
|
310
|
+
exports.useLocation = useLocation;
|
|
311
|
+
exports.useNavigation = useNavigation;
|
|
312
|
+
exports.useParams = useParams;
|
|
313
|
+
exports.useSearchParams = useSearchParams;
|
|
314
|
+
exports.useSsrData = useSsrData;
|
|
315
|
+
//# sourceMappingURL=index.cjs.map
|
|
316
|
+
//# sourceMappingURL=index.cjs.map
|