@aminnairi/react-router 0.1.0

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.
Files changed (4) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +636 -0
  3. package/index.tsx +204 -0
  4. package/package.json +28 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Amin NAIRI
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,636 @@
1
+ # @aminnairi/react-router
2
+
3
+ Type-safe router for the React library
4
+
5
+ ## Requirements
6
+
7
+ - [Node](https://nodejs.org/)
8
+ - [NPM](https://npmjs.com/)
9
+
10
+ ## Usage
11
+
12
+ ### Project initialization
13
+
14
+ ```bash
15
+ npm create vite -- --template react-ts project
16
+ cd project
17
+ ```
18
+
19
+ ### Dependencies installation
20
+ ```bash
21
+ npm install
22
+ ```
23
+
24
+ ### Library installation
25
+
26
+ ```bash
27
+ npm install @aminnairi/react-router
28
+ ```
29
+
30
+ ### Setup
31
+
32
+ ```bash
33
+ mkdir src/router
34
+ mkdir src/router/pages
35
+ touch src/router/pages/home.tsx
36
+ ```
37
+
38
+ ```tsx
39
+ import { createPage } from "@aminnairi/react-router";
40
+
41
+ export const home = createPage({
42
+ path: "/",
43
+ element: () => <h1>Home page</h1>
44
+ });
45
+ ```
46
+
47
+ ```bash
48
+ touch src/router/fallback.tsx
49
+ ```
50
+
51
+ ```tsx
52
+ import { home } from "./pages/home";
53
+
54
+ export const Fallback = () => {
55
+ return (
56
+ <button onClick={home.navigate}>
57
+ Go back home
58
+ </button>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ```bash
64
+ touch src/router/issue.tsx
65
+ ```
66
+
67
+ ```tsx
68
+ import { Fragment } from "react";
69
+ import { home } from "./pages/home";
70
+
71
+ export const Issue = () => {
72
+ return (
73
+ <Fragment>
74
+ <h1>An issue occurred</h1>
75
+ <button onClick={home.navigate}>
76
+ Go back home
77
+ </button>
78
+ </Fragment>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ```bash
84
+ touch src/router/index.ts
85
+ ```
86
+
87
+ ```tsx
88
+ import { createRouter } from "@aminnairi/react-router";
89
+ import { Fallback } from "./router/fallback";
90
+ import { Issue } from "./router/issue";
91
+ import { home } from "./router/pages/home";
92
+
93
+ export const router = createRouter({
94
+ fallback: Fallback,
95
+ issue: Issue,
96
+ routes: [
97
+ home.page
98
+ ]
99
+ });
100
+ ```
101
+
102
+ ```bash
103
+ touch src/App.tsx
104
+ ```
105
+
106
+ ```tsx
107
+ import { router } from "./router";
108
+
109
+ export default function App() {
110
+ return (
111
+ <router.View />
112
+ );
113
+ }
114
+ ```
115
+
116
+ ### Startup
117
+
118
+ ```bash
119
+ npm run dev
120
+ ```
121
+
122
+ ## API
123
+
124
+ ### createPage
125
+
126
+ Creates a new page definition that can then later be used to create a router. It takes the path of the page to create as well as the element that needs to be rendered when a client navigates to this page.
127
+
128
+ ```tsx
129
+ import { createPage } from "@aminnairi/react-router";
130
+
131
+ createPage({
132
+ path: "/",
133
+ element: () => (
134
+ <h1>Home</h1>
135
+ )
136
+ });
137
+ ```
138
+
139
+ You can then inject the page inside a router.
140
+
141
+ ```tsx
142
+ import { createPage, createRouter } from "@aminnairi/react-router";
143
+
144
+ const home = createPage({
145
+ path: "/",
146
+ element: () => (
147
+ <h1>
148
+ Home
149
+ </h1>
150
+ )
151
+ });
152
+
153
+ createRouter({
154
+ fallback: () => (
155
+ <h1>
156
+ Not found
157
+ </h1>
158
+ ),
159
+ issue: () => (
160
+ <h1>
161
+ An error occurred
162
+ </h1>
163
+ ),
164
+ pages: [
165
+ home.page
166
+ ]
167
+ });
168
+ ```
169
+
170
+ You can define a page that has dynamic parameters, and get back into the element the needed parameters.
171
+
172
+ ```tsx
173
+ import { createPage } from "@aminnairi/react-router";
174
+
175
+ createPage({
176
+ path: "/users/:user",
177
+ element: ({ parameters: { user }}) => (
178
+ <h1>
179
+ User#{user}
180
+ </h1>
181
+ )
182
+ });
183
+ ```
184
+
185
+ And if you can have of course more than one dynamic parameter.
186
+
187
+ ```tsx
188
+ import { createPage } from "@aminnairi/react-router";
189
+
190
+ createPage({
191
+ path: "/users/:user/articles/:article",
192
+ element: ({ parameters: { user, article }}) => (
193
+ <h1>
194
+ Article#{article } of user#{user}
195
+ </h1>
196
+ )
197
+ });
198
+ ```
199
+
200
+ You can also navigate to one page from another.
201
+
202
+ ```tsx
203
+ import { Fragment } from "react";
204
+ import { createPage } from "@aminnairi/react-router";
205
+
206
+ const login = createPage({
207
+ path: "/login",
208
+ element: () => (
209
+ <h1>
210
+ Login
211
+ </h1>
212
+ )
213
+ });
214
+
215
+ const about = createPage({
216
+ path: "/about",
217
+ element: () => (
218
+ <Fragment>
219
+ <h1>
220
+ About Us
221
+ </h1>
222
+ <button onClick={() => login.navigate({})}>
223
+ </button>
224
+ </Fragment>
225
+ )
226
+ });
227
+
228
+ createPage({
229
+ path: "/",
230
+ element: () => (
231
+ <Fragment>
232
+ <h1>
233
+ Home
234
+ </h1>
235
+ <button onClick={about.navigate}>
236
+ About Us
237
+ </button>
238
+ </Fragment>
239
+ )
240
+ });
241
+ ```
242
+
243
+ And you can of course navigate to pages that have dynamic parameters as well.
244
+
245
+ ```tsx
246
+ import { Fragment } from "react";
247
+ import { createPage } from "@aminnairi/react-router";
248
+
249
+ const user = createPage({
250
+ path: "/users/:user",
251
+ element: ({ parameters: { user }}) => (
252
+ <h1>
253
+ User#{user}
254
+ </h1>
255
+ )
256
+ });
257
+
258
+ createPage({
259
+ path: "/",
260
+ element: () => (
261
+ <Fragment>
262
+ <h1>
263
+ Home
264
+ </h1>
265
+ <button onClick={() => user.navigate({ user: "123" })}>
266
+ User#123
267
+ </button>
268
+ </Fragment>
269
+ )
270
+ });
271
+ ```
272
+
273
+ ### createRouter
274
+
275
+ Creates a router that you can then use to display the view, which is the page matching the current browser's location.
276
+
277
+ ```tsx
278
+ import { Fragment, StrictMode } from "react";
279
+ import { createRoot } from "react-dom/client";
280
+ import { createRouter, createPage } from "@aminnairi/react-router";
281
+
282
+ const home = createPage({
283
+ path: "/",
284
+ element: () => (
285
+ <h1>Home</h1>
286
+ )
287
+ });
288
+
289
+ const router = createRouter({
290
+ fallback: () => (
291
+ <h1>Not found</h1>
292
+ ),
293
+ issue: () => (
294
+ <h1>An error occurred</h1>
295
+ ),
296
+ pages: [
297
+ home.page
298
+ ]
299
+ });
300
+
301
+ const rootElement = document.getElementById("root");
302
+
303
+ if (!rootElement) {
304
+ throw new Error("Root element not found");
305
+ }
306
+
307
+ const root = createRoot(rootElement);
308
+
309
+ const App = () => {
310
+ return (
311
+ <Fragment>
312
+ <header>
313
+ <h1>App</h1>
314
+ </header>
315
+ <main>
316
+ <router.View />
317
+ </main>
318
+ <footer>
319
+ Credit © Yourself 2025
320
+ </footer>
321
+ </Fragment>
322
+ );
323
+ }
324
+
325
+ root.render(
326
+ <StrictMode>
327
+ <App />
328
+ </StrictMode>
329
+ );
330
+ ```
331
+
332
+ You can also activate the View Transition Web API if you want before each page renders. This is nice because by default, the browser already has some styling that allows for a smooth and simple transition between pages.
333
+
334
+ All you have to do is to set the `withViewTransition` property to `true` in the arguments of the `createRouter` function. By default, its value is set to `false` if not provided in the arguments of the `createRouter` function.
335
+
336
+ ```tsx
337
+ import { Fragment, StrictMode } from "react";
338
+ import { createRoot } from "react-dom/client";
339
+ import { createRouter, createPage } from "@aminnairi/react-router";
340
+
341
+ const home = createPage({
342
+ path: "/",
343
+ element: () => (
344
+ <h1>Home</h1>
345
+ )
346
+ });
347
+
348
+ const router = createRouter({
349
+ transition: true,
350
+ fallback: () => (
351
+ <h1>Not found</h1>
352
+ ),
353
+ issue: () => (
354
+ <h1>An error occurred</h1>
355
+ ),
356
+ pages: [
357
+ home.page
358
+ ]
359
+ });
360
+
361
+ const rootElement = document.getElementById("root");
362
+
363
+ if (!rootElement) {
364
+ throw new Error("Root element not found");
365
+ }
366
+
367
+ const root = createRoot(rootElement);
368
+
369
+ const App = () => {
370
+ return (
371
+ <Fragment>
372
+ <header>
373
+ <h1>App</h1>
374
+ </header>
375
+ <main>
376
+ <router.View />
377
+ </main>
378
+ <footer>
379
+ Credit © Yourself 2025
380
+ </footer>
381
+ </Fragment>
382
+ );
383
+ }
384
+
385
+ root.render(
386
+ <StrictMode>
387
+ <App />
388
+ </StrictMode>
389
+ );
390
+ ```
391
+
392
+ The `createRouter` takes a functional component that allow you to react to error in case a component throws. You can use the props to get a property `error` containing the error that has been thrown as well as a `reset` function that allow you to reset the error.
393
+
394
+ ```tsx
395
+ import { Fragment, StrictMode } from "react";
396
+ import { createRoot } from "react-dom/client";
397
+ import { createRouter, createPage } from "@aminnairi/react-router";
398
+
399
+ const home = createPage({
400
+ path: "/",
401
+ element: () => (
402
+ <h1>Home</h1>
403
+ )
404
+ });
405
+
406
+ const router = createRouter({
407
+ transition: true,
408
+ fallback: () => (
409
+ <h1>Not found</h1>
410
+ ),
411
+ issue: ({ error, reset }) => (
412
+ return (
413
+ <Fragment>
414
+ <h1>Error</h1>
415
+ <p>{error.message}</p>
416
+ <button onClick={reset}>Reset</button>
417
+ </Fragment>
418
+ );
419
+ ),
420
+ pages: [
421
+ home.page
422
+ ]
423
+ });
424
+
425
+ const rootElement = document.getElementById("root");
426
+
427
+ if (!rootElement) {
428
+ throw new Error("Root element not found");
429
+ }
430
+
431
+ const root = createRoot(rootElement);
432
+
433
+ const App = () => {
434
+ return (
435
+ <Fragment>
436
+ <header>
437
+ <h1>App</h1>
438
+ </header>
439
+ <main>
440
+ <router.View />
441
+ </main>
442
+ <footer>
443
+ Credit © Yourself 2025
444
+ </footer>
445
+ </Fragment>
446
+ );
447
+ }
448
+
449
+ root.render(
450
+ <StrictMode>
451
+ <App />
452
+ </StrictMode>
453
+ );
454
+ ```
455
+
456
+ You can also define this function from the outside by using the `createIssue` function.
457
+
458
+ ```tsx
459
+ import { Fragment, StrictMode } from "react";
460
+ import { createRoot } from "react-dom/client";
461
+ import { createRouter, createPage, createIssue } from "@aminnairi/react-router";
462
+
463
+ const home = createPage({
464
+ path: "/",
465
+ element: () => (
466
+ <h1>Home</h1>
467
+ )
468
+ });
469
+
470
+ const Fallback = () => {
471
+ return (
472
+ <h1>Not found</h1>
473
+ );
474
+ }
475
+
476
+ const Issue = createIssue(({ error, reset }) => (
477
+ return (
478
+ <Fragment>
479
+ <h1>Error</h1>
480
+ <p>{error.message}</p>
481
+ <button onClick={reset}>Reset</button>
482
+ </Fragment>
483
+ );
484
+ ));
485
+
486
+ const router = createRouter({
487
+ transition: true,
488
+ fallback: Fallback,
489
+ issue: Issue,
490
+ pages: [
491
+ home.page
492
+ ]
493
+ });
494
+
495
+ const rootElement = document.getElementById("root");
496
+
497
+ if (!rootElement) {
498
+ throw new Error("Root element not found");
499
+ }
500
+
501
+ const root = createRoot(rootElement);
502
+
503
+ const App = () => {
504
+ return (
505
+ <Fragment>
506
+ <header>
507
+ <h1>App</h1>
508
+ </header>
509
+ <main>
510
+ <router.View />
511
+ </main>
512
+ <footer>
513
+ Credit © Yourself 2025
514
+ </footer>
515
+ </Fragment>
516
+ );
517
+ }
518
+
519
+ root.render(
520
+ <StrictMode>
521
+ <App />
522
+ </StrictMode>
523
+ );
524
+ ```
525
+
526
+ ### doesRouteMatchPath
527
+
528
+ Return a boolean in case a route matches a path. A route is a URI that looks something like `/users/:user/articles` and a path is the browser's location pathname that looks something like `/users/123/articles`.
529
+
530
+ This function is mainly used in the internals of the `createRouter` and in most case should not be necessary.
531
+
532
+ ```typescript
533
+ import { doesRouteMatchPath } from "@aminnairi/react-router";
534
+
535
+ doesRoutePatchPath("/", "/"); // true
536
+
537
+ doesRoutePatchPath("/", "/about"); // false
538
+
539
+ doesRoutePatchPath("/users/:user", "/users/123"); // true
540
+
541
+ doesRoutePatchPath("/users/:user", "/users/123/articles"); // false
542
+ ```
543
+
544
+ ### getParameters
545
+
546
+ Return an object in case a route matches a path, with its dynamic parameters as output. It returns a generic `object` type in case no dynamic parameters are found in the URI. Note that the parameters are always strings, if you need to, convert them to other types explicitely.
547
+
548
+ This function is mainly used in the internals of the `createRouter` and in most case should not be necessary.
549
+
550
+ ```typescript
551
+ import { getParameters } from "@aminnairi/react-router";
552
+
553
+ getParameters("/", "/"); // object
554
+
555
+ getParameters("/", "/about"); // object
556
+
557
+ getParameters("/users/:user", "/users/123"); // { user: "123" }
558
+
559
+ getParameters("/users/:user", "/users/123/articles"); // { user: "123" }
560
+ ```
561
+
562
+ ### findPage
563
+
564
+ Return a page that matches the `window.location.pathname` property containing the current URI of the page from an array of pages.
565
+
566
+ If it does not match any pages, it returns `undefined` instead.
567
+
568
+ This function is mainly used in the internals of the `createRouter` and in most case should not be necessary.
569
+
570
+ ```tsx
571
+ import { findPage, createPage } from "@aminnairi/react-router";
572
+
573
+ const home = createPage({
574
+ path: "/",
575
+ element: () => <h1>Home</h1>
576
+ });
577
+
578
+ const about = createPage({
579
+ path: "/about",
580
+ element: () => <h1>About</h1>
581
+ });
582
+
583
+ const login = createPage({
584
+ path: "/login",
585
+ element: () => <h1>Login</h1>
586
+ });
587
+
588
+ const pages = [
589
+ home.page,
590
+ about.page,
591
+ login.page
592
+ ];
593
+
594
+ const foundPage = findPage({
595
+ pages
596
+ });
597
+
598
+ if (foundPage) {
599
+ console.log("Found a page matching the current location");
600
+ console.log(foundPage.path);
601
+ } else {
602
+ console.log("No page matching the current location.");
603
+ }
604
+ ```
605
+
606
+ ## Features
607
+
608
+ ### TypeScript
609
+
610
+ This library has been written in TypeScript from the ground up, no manual definition types created, only pure TypeScript.
611
+
612
+ Type-safety has been the #1 goal, this means that you can fearlessly refactor your code without forgetting to update one part of your code that might break, types got you covered.
613
+
614
+ ### No codegen
615
+
616
+ Code generation is useful in environment where multiple languages may be used, but in the case of a Web application written in TypeScript, there is no need for any codegen at all, thus reducing the surface of errors possibly generated by such tools, and greatly reducing complexity when setting up a router.
617
+
618
+ ### Simplicity
619
+
620
+ This library does nothing more other than abstracting for your the complexity of using the History Web API, as well as providing you with type safety out of the box.
621
+
622
+ This means that you can use this library with other popular solutions for handling metadata for instance.
623
+
624
+ ### Transition
625
+
626
+ Support for the View Transition API is built-in and allows for painless and smooth view transition out-of-the-box without having to do anything.
627
+
628
+ This can also easily be disabled if needed.
629
+
630
+ ### Error handling
631
+
632
+ Never fear having a blank page again when a component throws. This library lets you define a functional component that will answer to any error that might be raised by any pages so that you can react accordingly by providing a nice and friendly error page instead of a blank or white page.
633
+
634
+ ## License
635
+
636
+ See [`LICENSE`](./LICENSE).
package/index.tsx ADDED
@@ -0,0 +1,204 @@
1
+ import { useEffect, useState, FunctionComponent, useMemo, Component, PropsWithChildren } from "react";
2
+
3
+ export type AbsolutePath<Path extends string> =
4
+ Path extends `${infer Start}:${string}/${infer Rest}`
5
+ ? `${Start}${string}/${AbsolutePath<Rest>}`
6
+ : Path extends `${infer Start}:${string}`
7
+ ? `${Start}${string}`
8
+ : Path;
9
+
10
+ export type Parameters<Path extends string> =
11
+ Path extends `${string}/:${infer Segment}/${infer Rest}`
12
+ ? { [K in Segment]: string } & Parameters<`/${Rest}`>
13
+ : Path extends `${string}/:${infer Segment}`
14
+ ? { [K in Segment]: string }
15
+ : object;
16
+
17
+ export type GoToPageFunction<Path extends string> = (path: AbsolutePath<Path>) => void;
18
+
19
+ export interface PageComponentProps<Path extends string> {
20
+ parameters: Parameters<Path>,
21
+ }
22
+
23
+ export interface Page<Path extends string> {
24
+ path: Path,
25
+ element: FunctionComponent<PageComponentProps<Path>>
26
+ }
27
+
28
+ export interface CreateRouterOptions {
29
+ transition?: boolean,
30
+ pages: Array<Page<string>>
31
+ fallback: FunctionComponent
32
+ issue: FunctionComponent<IssueProps>
33
+ }
34
+
35
+ export interface CreateRouteOutput<Path extends string> {
36
+ page: Page<Path>,
37
+ navigate: (parameters: Parameters<Path>) => void
38
+ }
39
+
40
+ export interface FindPageOptions {
41
+ pages: Array<Page<string>>
42
+ }
43
+
44
+ export const createPage = <Path extends string>(page: Page<Path>): CreateRouteOutput<Path> => {
45
+ const navigate = (parameters: Parameters<Path>, replace: boolean = false) => {
46
+ const pathWithParameters = Object.entries(parameters).reduce((path, [parameterName, parameterValue]) => {
47
+ return path.replace(`:${parameterName}`, parameterValue);
48
+ }, page.path as string);
49
+
50
+ if (replace) {
51
+ window.history.replaceState(null, pathWithParameters, pathWithParameters);
52
+ } else {
53
+ window.history.pushState(null, pathWithParameters, pathWithParameters);
54
+ }
55
+
56
+ window.dispatchEvent(new CustomEvent("popstate"));
57
+ };
58
+
59
+ return {
60
+ page,
61
+ navigate
62
+ };
63
+ }
64
+
65
+ export const doesRouteMatchPath = (path: string, route: string): boolean => {
66
+ const pathParts = path.split("/").filter(Boolean);
67
+ const routeParts = route.split("/").filter(Boolean);
68
+
69
+ return (
70
+ pathParts.length === routeParts.length &&
71
+ pathParts.every((part, index) => part.startsWith(":") || part === routeParts[index])
72
+ );
73
+ }
74
+
75
+ export const getParameters = <Path extends string>(config: { path: Path; route: string }): Parameters<Path> => {
76
+ return Object.fromEntries(
77
+ config.path
78
+ .split("/")
79
+ .map((part, index) => (part.startsWith(":") ? [part.slice(1), config.route.split("/")[index]] : null))
80
+ .filter((entry): entry is [string, string] => entry !== null)
81
+ ) as Parameters<Path>;
82
+ }
83
+
84
+ const findPage = ({ pages }: FindPageOptions) => {
85
+ const foundPage = pages.find(route => {
86
+ return doesRouteMatchPath(route.path, window.location.pathname);
87
+ });
88
+
89
+ return foundPage;
90
+ };
91
+
92
+ export interface IssueProps {
93
+ error: Error,
94
+ reset: () => void,
95
+ }
96
+
97
+ export interface ErrorBoundaryProps {
98
+ fallback: FunctionComponent<IssueProps>,
99
+ transition: boolean
100
+ }
101
+
102
+ export interface ErrorBoundaryState {
103
+ error: Error | null
104
+ }
105
+
106
+ export class ErrorBoundary extends Component<PropsWithChildren<ErrorBoundaryProps>, ErrorBoundaryState> {
107
+ constructor(props: ErrorBoundaryProps) {
108
+ super(props);
109
+
110
+ this.state = { error: null };
111
+ }
112
+
113
+ static getDerivedStateFromError(error: unknown) {
114
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
115
+
116
+ return { error: normalizedError };
117
+ }
118
+
119
+ render() {
120
+ const viewTransitionSupported = typeof document.startViewTransition === "function";
121
+
122
+ if (this.state.error) {
123
+ const reset = () => {
124
+ if (this.props.transition && viewTransitionSupported) {
125
+ document.startViewTransition(() => {
126
+ this.setState({
127
+ error: null
128
+ });
129
+ });
130
+
131
+ return;
132
+ }
133
+
134
+ this.setState({
135
+ error: null
136
+ });
137
+ }
138
+
139
+ return this.props.fallback({
140
+ error: this.state.error,
141
+ reset
142
+ });
143
+ }
144
+
145
+ return this.props.children;
146
+ }
147
+ }
148
+
149
+ export const createIssue = (issue: FunctionComponent<IssueProps>) => {
150
+ return issue;
151
+ }
152
+
153
+ export const createRouter = ({ pages, fallback, transition: withViewTransition, issue }: CreateRouterOptions) => {
154
+ const View = () => {
155
+ const [page, setPage] = useState(findPage({ pages }));
156
+ const shouldTransitionBetweenPages = useMemo(() => typeof document.startViewTransition === "function" && withViewTransition ? true : false, [withViewTransition]);
157
+ const Fallback = useMemo(() => fallback, []);
158
+
159
+ const parameters = useMemo(() => {
160
+ if (page) {
161
+ return getParameters({
162
+ path: page.path,
163
+ route: window.location.pathname
164
+ })
165
+ }
166
+
167
+ return {};
168
+ }, [page]);
169
+
170
+ useEffect(() => {
171
+ const onWindowPopstate = () => {
172
+ const foundPage = findPage({ pages });
173
+
174
+ if (shouldTransitionBetweenPages) {
175
+ document.startViewTransition(() => {
176
+ setPage(foundPage);
177
+ });
178
+ } else {
179
+ setPage(foundPage);
180
+ }
181
+ };
182
+
183
+ window.addEventListener("popstate", onWindowPopstate);
184
+
185
+ return () => {
186
+ window.removeEventListener("popstate", onWindowPopstate);
187
+ }
188
+ }, []);
189
+
190
+ if (page) {
191
+ return (
192
+ <ErrorBoundary fallback={issue} transition={shouldTransitionBetweenPages}>
193
+ {<page.element parameters={parameters} />}
194
+ </ErrorBoundary>
195
+ );
196
+ }
197
+
198
+ return <Fallback />;
199
+ };
200
+
201
+ return {
202
+ View
203
+ };
204
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@aminnairi/react-router",
4
+ "description": "Type-safe router for the React library",
5
+ "version": "0.1.0",
6
+ "homepage": "https://github.com/aminnairi/react-router#readme",
7
+ "license": "MIT",
8
+ "bugs": {
9
+ "url": "https://github.com/aminnairi/react-router/issues"
10
+ },
11
+ "author": {
12
+ "name": "Amin NAIRI",
13
+ "url": "https://github.com/aminnairi"
14
+ },
15
+ "repository": {
16
+ "url": "git+https://github.com/aminnairi/react-router.git"
17
+ },
18
+ "keywords": [
19
+ "react",
20
+ "router",
21
+ "hook",
22
+ "typescript",
23
+ "transition"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": "^18.0.0"
27
+ }
28
+ }