@aminnairi/react-router 2.2.0 → 3.0.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.
package/README.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  Type-safe router for the React library
4
4
 
5
+ ## Documentation
6
+
7
+ - [Requirements](#requirements)
8
+ - [Usage](#usage)
9
+ - [Project initialization](#project-initialization)
10
+ - [Dependencies installation](#dependencies-installation)
11
+ - [Library installation](#library-installation)
12
+ - [Setup](#setup)
13
+ - [Startup](#startup)
14
+ - [API](#api)
15
+ - [createPage](#createpage)
16
+ - [useNavigateToPage](#usenavigatetopage)
17
+ - [createRouter](#createrouter)
18
+ - [useIsActivePage](#useisactivepage)
19
+ - [useLocale](#uselocale)
20
+ - [usePrefix](#useprefix)
21
+ - [usePath](#usepath)
22
+ - [Features](#features)
23
+ - [TypeScript](#typescript)
24
+ - [No codegen](#no-codegen)
25
+ - [Simplicity](#simplicity)
26
+ - [Transition](#transition)
27
+ - [Error handling](#error-handling)
28
+ - [License](#license)
29
+ - [Changelogs](#changelogs)
30
+ - [Versions](#versions)
31
+ - [3.0.0](#300)
32
+ - [2.1.0](#210)
33
+ - [2.0.1](#201)
34
+ - [2.0.0](#200)
35
+ - [1.1.0](#110)
36
+ - [1.0.1](#101)
37
+ - [1.0.0](#100)
38
+ - [0.1.1](#011)
39
+ - [0.1.0](#010)
40
+
5
41
  ## Requirements
6
42
 
7
43
  - [Node](https://nodejs.org/)
@@ -58,7 +94,7 @@ import { home } from "./pages/home";
58
94
  export const Fallback = () => {
59
95
  const navigateToHomePage = useNavigateToPage(home);
60
96
 
61
- return <button onClick={() => navigateToHomePage({})}>Go back home</button>;
97
+ return <button onClick={navigateToHomePage}>Go back home</button>;
62
98
  };
63
99
  ```
64
100
 
@@ -77,7 +113,7 @@ export const Issue = () => {
77
113
  return (
78
114
  <Fragment>
79
115
  <h1>An issue occurred</h1>
80
- <button onClick={() => navigateToHomePage({})}>Go back home</button>
116
+ <button onClick={navigateToHomePage}>Go back home</button>
81
117
  </Fragment>
82
118
  );
83
119
  };
@@ -93,7 +129,7 @@ import { Fallback } from "./fallback";
93
129
  import { Issue } from "./issue";
94
130
  import { home } from "./pages/home";
95
131
 
96
- export const router = createRouter({
132
+ export const { RouterProvider, RouterView } = createRouter({
97
133
  fallback: Fallback,
98
134
  issue: Issue,
99
135
  pages: [home],
@@ -105,10 +141,10 @@ touch src/App.tsx
105
141
  ```
106
142
 
107
143
  ```tsx
108
- import { router } from "./router";
144
+ import { RouterView } from "./router";
109
145
 
110
146
  export default function App() {
111
- return <router.View />;
147
+ return <RouterView />;
112
148
  }
113
149
  ```
114
150
 
@@ -119,7 +155,7 @@ touch src/main.tsx
119
155
  ```tsx
120
156
  import { StrictMode } from "react";
121
157
  import { createRoot } from "react-dom/client";
122
- import { router } from "./router";
158
+ import { RouterProvider } from "./router";
123
159
  import App from "./App";
124
160
 
125
161
  const rootElement = document.getElementById("root");
@@ -130,9 +166,9 @@ if (!rootElement) {
130
166
 
131
167
  createRoot(rootElement).render(
132
168
  <StrictMode>
133
- <router.Provider>
169
+ <RouterProvider>
134
170
  <App />
135
- </router.Provider>
171
+ </RouterProvider>
136
172
  </StrictMode>,
137
173
  );
138
174
  ```
@@ -213,9 +249,11 @@ createPage({
213
249
 
214
250
  You can navigate from one page from another.
215
251
 
252
+ Note: This hook is returned from `createRouter`, not imported directly from the library.
253
+
216
254
  ```tsx
217
255
  import { Fragment } from "react";
218
- import { createPage, useNavigateToPage } from "@aminnairi/react-router";
256
+ import { createPage, createRouter } from "@aminnairi/react-router";
219
257
 
220
258
  const login = createPage({
221
259
  path: "/login",
@@ -232,12 +270,18 @@ const about = createPage({
232
270
  return (
233
271
  <Fragment>
234
272
  <h1>About Us</h1>
235
- <button onClick={() => navigateToLoginPage({})}>Login</button>
273
+ <button onClick={navigateToLoginPage}>Login</button>
236
274
  </Fragment>
237
275
  );
238
276
  },
239
277
  });
240
278
 
279
+ const { useNavigateToPage } = createRouter({
280
+ fallback: () => <h1>Not found</h1>,
281
+ issue: () => <h1>An error occurred</h1>,
282
+ pages: [login, about],
283
+ });
284
+
241
285
  createPage({
242
286
  path: "/",
243
287
  element: function Home() {
@@ -246,7 +290,7 @@ createPage({
246
290
  return (
247
291
  <Fragment>
248
292
  <h1>Home</h1>
249
- <button onClick={() => navigateToAboutPage({})}>About Us</button>
293
+ <button onClick={navigateToAboutPage}>About Us</button>
250
294
  </Fragment>
251
295
  );
252
296
  },
@@ -299,7 +343,7 @@ const home = createPage({
299
343
  },
300
344
  });
301
345
 
302
- const router = createRouter({
346
+ const { RouterProvider, RouterView } = createRouter({
303
347
  fallback: () => <h1>Not found</h1>,
304
348
  issue: () => <h1>An error occurred</h1>,
305
349
  pages: [home],
@@ -320,7 +364,7 @@ const App = () => {
320
364
  <h1>App</h1>
321
365
  </header>
322
366
  <main>
323
- <router.View />
367
+ <RouterView />
324
368
  </main>
325
369
  <footer>Credit © Yourself 2025</footer>
326
370
  </Fragment>
@@ -329,18 +373,18 @@ const App = () => {
329
373
 
330
374
  root.render(
331
375
  <StrictMode>
332
- <router.Provider>
376
+ <RouterProvider>
333
377
  <App />
334
- </router.Provider>
378
+ </RouterProvider>
335
379
  </StrictMode>,
336
380
  );
337
381
  ```
338
382
 
339
383
  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.
340
384
 
341
- All you have to do is to provide a `transition` function in the arguments of the `createRouter` function. This function receives the navigation direction (`"pushstate"` or `"popstate"`) and a `next` callback to render the next page.
385
+ All you have to do is to provide a `transition` function in the arguments of the `createRouter` function. This function receives the navigation direction (`"forward"` or `"backward"`) and a `next` callback to render the next page.
342
386
 
343
- This library also exports a `slideFadeTransition` that you can use out-of-the-box.
387
+ This library also exports several transitions that you can use out-of-the-box: `slideHorizontalTransition`, `slideVerticalTransition`, `crossFadeTransition`, and `scaleFadeTransition`.
344
388
 
345
389
  ```tsx
346
390
  import { Fragment, StrictMode } from "react";
@@ -348,7 +392,7 @@ import { createRoot } from "react-dom/client";
348
392
  import {
349
393
  createRouter,
350
394
  createPage,
351
- slideFadeTransition,
395
+ slideHorizontalTransition,
352
396
  } from "@aminnairi/react-router";
353
397
 
354
398
  const home = createPage({
@@ -358,8 +402,8 @@ const home = createPage({
358
402
  },
359
403
  });
360
404
 
361
- const router = createRouter({
362
- transition: slideFadeTransition,
405
+ const { RouterProvider, RouterView } = createRouter({
406
+ transition: slideHorizontalTransition,
363
407
  fallback: () => <h1>Not found</h1>,
364
408
  issue: () => <h1>An error occurred</h1>,
365
409
  pages: [home],
@@ -380,7 +424,7 @@ const App = () => {
380
424
  <h1>App</h1>
381
425
  </header>
382
426
  <main>
383
- <router.View />
427
+ <RouterView />
384
428
  </main>
385
429
  <footer>Credit © Yourself 2025</footer>
386
430
  </Fragment>
@@ -389,9 +433,9 @@ const App = () => {
389
433
 
390
434
  root.render(
391
435
  <StrictMode>
392
- <router.Provider>
436
+ <RouterProvider>
393
437
  <App />
394
- </router.Provider>
438
+ </RouterProvider>
395
439
  </StrictMode>,
396
440
  );
397
441
  ```
@@ -410,13 +454,13 @@ const home = createPage({
410
454
  },
411
455
  });
412
456
 
413
- const router = createRouter({
457
+ const { RouterProvider, RouterView } = createRouter({
414
458
  fallback: () => <h1>Not found</h1>,
415
- issue: ({ error, reset }) => (
459
+ issue: ({ error, resetError }) => (
416
460
  <Fragment>
417
461
  <h1>Error</h1>
418
462
  <p>{error.message}</p>
419
- <button onClick={reset}>Reset</button>
463
+ <button onClick={resetError}>Reset</button>
420
464
  </Fragment>
421
465
  ),
422
466
  pages: [home],
@@ -437,7 +481,7 @@ const App = () => {
437
481
  <h1>App</h1>
438
482
  </header>
439
483
  <main>
440
- <router.View />
484
+ <RouterView />
441
485
  </main>
442
486
  <footer>Credit © Yourself 2025</footer>
443
487
  </Fragment>
@@ -446,19 +490,19 @@ const App = () => {
446
490
 
447
491
  root.render(
448
492
  <StrictMode>
449
- <router.Provider>
493
+ <RouterProvider>
450
494
  <App />
451
- </router.Provider>
495
+ </RouterProvider>
452
496
  </StrictMode>,
453
497
  );
454
498
  ```
455
499
 
456
- You can also define this function from the outside by using the `createIssue` function.
500
+ You can also define the issue component from the outside.
457
501
 
458
502
  ```tsx
459
503
  import { Fragment, StrictMode } from "react";
460
504
  import { createRoot } from "react-dom/client";
461
- import { createRouter, createPage, createIssue } from "@aminnairi/react-router";
505
+ import { createRouter, createPage } from "@aminnairi/react-router";
462
506
 
463
507
  const home = createPage({
464
508
  path: "/",
@@ -471,15 +515,15 @@ const Fallback = () => {
471
515
  return <h1>Not found</h1>;
472
516
  };
473
517
 
474
- const Issue = createIssue(({ error, reset }) => (
518
+ const Issue = ({ error, resetError }: { error: Error; resetError: () => void }) => (
475
519
  <Fragment>
476
520
  <h1>Error</h1>
477
521
  <p>{error.message}</p>
478
- <button onClick={reset}>Reset</button>
522
+ <button onClick={resetError}>Reset</button>
479
523
  </Fragment>
480
- ));
524
+ );
481
525
 
482
- const router = createRouter({
526
+ const { RouterProvider, RouterView } = createRouter({
483
527
  fallback: Fallback,
484
528
  issue: Issue,
485
529
  pages: [home],
@@ -500,7 +544,7 @@ const App = () => {
500
544
  <h1>App</h1>
501
545
  </header>
502
546
  <main>
503
- <router.View />
547
+ <RouterView />
504
548
  </main>
505
549
  <footer>Credit © Yourself 2025</footer>
506
550
  </Fragment>
@@ -509,9 +553,9 @@ const App = () => {
509
553
 
510
554
  root.render(
511
555
  <StrictMode>
512
- <router.Provider>
556
+ <RouterProvider>
513
557
  <App />
514
- </router.Provider>
558
+ </RouterProvider>
515
559
  </StrictMode>,
516
560
  );
517
561
  ```
@@ -526,7 +570,6 @@ import { createRoot } from "react-dom/client";
526
570
  import {
527
571
  createRouter,
528
572
  createPage,
529
- createIssue,
530
573
  useNavigateToPage,
531
574
  } from "@aminnairi/react-router";
532
575
 
@@ -543,20 +586,20 @@ const Fallback = () => {
543
586
  return (
544
587
  <Fragment>
545
588
  <h1>Not found</h1>
546
- <button onClick={() => navigateToHomePage({})}>Go Back Home</button>
589
+ <button onClick={navigateToHomePage}>Go Back Home</button>
547
590
  </Fragment>
548
591
  );
549
592
  };
550
593
 
551
- const Issue = createIssue(({ error, reset }) => (
594
+ const Issue = ({ error, resetError }: { error: Error; resetError: () => void }) => (
552
595
  <Fragment>
553
596
  <h1>Error</h1>
554
597
  <p>{error.message}</p>
555
- <button onClick={reset}>Reset</button>
598
+ <button onClick={resetError}>Reset</button>
556
599
  </Fragment>
557
- ));
600
+ );
558
601
 
559
- const router = createRouter({
602
+ const { RouterProvider, RouterView } = createRouter({
560
603
  prefix: "/portfolio",
561
604
  fallback: Fallback,
562
605
  issue: Issue,
@@ -578,7 +621,7 @@ const App = () => {
578
621
  <h1>App</h1>
579
622
  </header>
580
623
  <main>
581
- <router.View />
624
+ <RouterView />
582
625
  </main>
583
626
  <footer>Credit © Yourself 2025</footer>
584
627
  </Fragment>
@@ -587,9 +630,9 @@ const App = () => {
587
630
 
588
631
  root.render(
589
632
  <StrictMode>
590
- <router.Provider>
633
+ <RouterProvider>
591
634
  <App />
592
- </router.Provider>
635
+ </RouterProvider>
593
636
  </StrictMode>,
594
637
  );
595
638
  ```
@@ -600,9 +643,11 @@ Allow you to create a function that can then be called to navigate to another pa
600
643
 
601
644
  It accepts a page that has been created using `createPage`.
602
645
 
646
+ Note: This hook is returned from `createRouter`, not imported directly from the library.
647
+
603
648
  ```tsx
604
649
  import { Fragment } from "react";
605
- import { createPage, useNavigateToPage } from "@aminnairi/react-router";
650
+ import { createPage, createRouter } from "@aminnairi/react-router";
606
651
 
607
652
  const home = createPage({
608
653
  path: "/",
@@ -611,6 +656,12 @@ const home = createPage({
611
656
  },
612
657
  });
613
658
 
659
+ const { useNavigateToPage } = createRouter({
660
+ fallback: () => <h1>Not found</h1>,
661
+ issue: () => <h1>An error occurred</h1>,
662
+ pages: [home],
663
+ });
664
+
614
665
  createPage({
615
666
  path: "/about",
616
667
  element: function About() {
@@ -619,7 +670,7 @@ createPage({
619
670
  return (
620
671
  <Fragment>
621
672
  <h1>About</h1>
622
- <button onClick={() => navigateToHomePage({})}>Home</button>
673
+ <button onClick={navigateToHomePage}>Home</button>
623
674
  </Fragment>
624
675
  );
625
676
  },
@@ -632,7 +683,7 @@ The parameters should always be provided as string, as they are the only data ty
632
683
 
633
684
  ```tsx
634
685
  import { Fragment } from "react";
635
- import { createPage, useNavigateToPage } from "@aminnairi/react-router";
686
+ import { createPage, createRouter } from "@aminnairi/react-router";
636
687
 
637
688
  const user = createPage({
638
689
  path: "/users/:user",
@@ -658,200 +709,146 @@ createPage({
658
709
  });
659
710
  ```
660
711
 
661
- ### useLink
712
+ ### useIsActivePage
662
713
 
663
- Allow you to navigate to another page using a JSX component instead of a callback as for the `useNavigateToPage` hook.
714
+ Allow you to check if a page is currently active.
664
715
 
665
- The created component is simply a `<a href="...">{children}</a>` under the hood which prevents the default behavior of the navigator which is to create a new HTTP request and to reload the page. The `href` attribute is computed from the page path and its parameters.
716
+ Note: This hook is returned from `createRouter`, not imported directly from the library.
666
717
 
667
718
  ```tsx
668
719
  import { Fragment } from "react";
669
- import { createPage, useLink } from "@aminnairi/react-router";
720
+ import { createPage, createRouter } from "@aminnairi/react-router";
670
721
 
671
- const user = createPage({
672
- path: "/users/:user",
673
- element: function User({ parameters: { user } }) {
674
- return <h1>User#{user}</h1>;
722
+ const home = createPage({
723
+ path: "/",
724
+ element: function Home() {
725
+ return <h1>Home</h1>;
675
726
  },
676
727
  });
677
728
 
678
- createPage({
729
+ const about = createPage({
679
730
  path: "/about",
680
731
  element: function About() {
681
- const Link = useLink(user);
682
-
683
- return (
684
- <Fragment>
685
- <h1>About</h1>
686
- <Link parameters={{ user: "123" }}>User#123</Link>
687
- </Fragment>
688
- );
732
+ return <h1>About</h1>;
689
733
  },
690
734
  });
691
- ```
692
-
693
- You can also provide a custom render function as the second argument to fully control the rendered output. This is useful when you want to use a different component library or need more control over the markup.
694
-
695
- ```tsx
696
- import { Fragment } from "react";
697
- import { createPage, useLink, UseLinkRenderFunction } from "@aminnairi/react-router";
698
735
 
699
- const user = createPage({
700
- path: "/users/:user",
701
- element: function User({ parameters: { user } }) {
702
- return <h1>User#{user}</h1>;
703
- },
736
+ const { useIsActivePage } = createRouter({
737
+ fallback: () => <h1>Not found</h1>,
738
+ issue: () => <h1>An error occurred</h1>,
739
+ pages: [home, about],
704
740
  });
705
741
 
706
742
  createPage({
707
- path: "/about",
708
- element: function About() {
709
- const CustomLink = useLink(user, ({ path, onClick, children }) => {
710
- return (
711
- <button className="nav-button" onClick={onClick}>
712
- {children}
713
- </button>
714
- );
715
- });
743
+ path: "/",
744
+ element: function Home() {
745
+ const isAboutActive = useIsActivePage(about);
716
746
 
717
747
  return (
718
748
  <Fragment>
719
- <h1>About</h1>
720
- <CustomLink parameters={{ user: "123" }}>User#123</CustomLink>
749
+ <h1>Home</h1>
750
+ <p>About page is {isAboutActive ? "active" : "not active"}</p>
721
751
  </Fragment>
722
752
  );
723
753
  },
724
754
  });
725
755
  ```
726
756
 
727
- The render function receives an object with the following properties:
728
- - `path`: The computed URL path with parameters filled in
729
- - `onClick`: The click event handler that prevents default navigation and handles routing
730
- - `children`: The children passed to the link component
731
-
732
- ### useSearch
757
+ ### useLocale
733
758
 
734
- Allow you to get one or more search query from the URL.
759
+ Allow you to get and set the current locale for internationalization.
735
760
 
736
- This will return an instance of the `URLSearchParams` Web API so that you can use you existing knowledge to manipulate the search queries easily.
761
+ Note: This hook is returned from `createRouter`, not imported directly from the library.
737
762
 
738
763
  ```tsx
739
- import { useMemo } from "react";
740
- import { createPage, useSearch } from "@aminnairi/react-router";
764
+ import { Fragment } from "react";
765
+ import { createPage, createRouter } from "@aminnairi/react-router";
741
766
 
742
- createPage({
743
- path: "/users",
767
+ const home = createPage({
768
+ path: "/",
744
769
  element: function Home() {
745
- const search = useSearch();
746
- const sortedByDate = useMemo(() => search.get("sort-by") === "date", [search]);
770
+ const { locale, setLocale } = useLocale();
747
771
 
748
772
  return (
749
- <h1>Users</h1>
750
- <p>Sorted by date: {sortedByDate ? "yes" : "no"}</p>
773
+ <Fragment>
774
+ <h1>Home</h1>
775
+ <p>Current locale: {locale ?? "none"}</p>
776
+ <button onClick={() => setLocale("en")}>English</button>
777
+ <button onClick={() => setLocale("fr")}>Français</button>
778
+ </Fragment>
751
779
  );
752
- }
780
+ },
781
+ });
782
+
783
+ const { RouterProvider, RouterView, useLocale } = createRouter({
784
+ locales: ["en", "fr"],
785
+ fallback: () => <h1>Not found</h1>,
786
+ issue: () => <h1>An error occurred</h1>,
787
+ pages: [home],
753
788
  });
754
789
  ```
755
790
 
756
- You cannot set the search queries for now, this will be added in future release of this library.
791
+ ### usePrefix
757
792
 
758
- ### useHash
793
+ Allow you to get the current route prefix.
759
794
 
760
- Allow you to get the hash, also called fragment, from the URL which is everything after the `#` symbol.
795
+ Note: This hook is returned from `createRouter`, not imported directly from the library.
761
796
 
762
797
  ```tsx
763
- import { createPage, useHash } from "@aminnairi/react-router";
798
+ import { Fragment } from "react";
799
+ import { createPage, createRouter } from "@aminnairi/react-router";
764
800
 
765
- createPage({
766
- path: "/oauth/callback",
767
- element: function OauthCallback() {
768
- const token = useHash();
801
+ const home = createPage({
802
+ path: "/",
803
+ element: function Home() {
804
+ const { prefix } = usePrefix();
769
805
 
770
- return <h1>You token is {token}</h1>;
806
+ return (
807
+ <Fragment>
808
+ <h1>Home</h1>
809
+ <p>Current prefix: {prefix ?? "none"}</p>
810
+ </Fragment>
811
+ );
771
812
  },
772
813
  });
773
- ```
774
-
775
- ## Internal API
776
-
777
- ### doesRouteMatchPath
778
-
779
- 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`.
780
-
781
- This function is mainly used in the internals of the `createRouter` and in most case should not be necessary.
782
-
783
- ```typescript
784
- import { doesRouteMatchPath } from "@aminnairi/react-router";
785
-
786
- doesRouteMatchPath("/", "/"); // true
787
-
788
- doesRouteMatchPath("/", "/about"); // false
789
-
790
- doesRouteMatchPath("/users/:user", "/users/123"); // true
791
-
792
- doesRouteMatchPath("/users/:user", "/users/123/articles"); // false
793
- ```
794
-
795
- You can also optionally provide a prefix.
796
-
797
- ```typescript
798
- import { doesRouteMatchPath } from "@aminnairi/react-router";
799
-
800
- doesRouteMatchPath("/", "/github", "/github"); // true
801
-
802
- doesRouteMatchPath("/", "/github/about", "/github"); // false
803
-
804
- doesRouteMatchPath("/users/:user", "/github/users/123", "/github"); // true
805
-
806
- doesRouteMatchPath("/users/:user", "/github/users/123/articles", "/github"); // false
807
- ```
808
-
809
- ### getParameters
810
-
811
- 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.
812
-
813
- This function is mainly used in the internals of the `createRouter` and in most case should not be necessary.
814
-
815
- ```typescript
816
- import { getParameters } from "@aminnairi/react-router";
817
-
818
- getParameters("/", "/"); // {}
819
-
820
- getParameters("/", "/about"); // {}
821
-
822
- getParameters("/users/:user", "/users/123"); // { user: "123" }
823
-
824
- getParameters("/users/:user", "/users/123/articles"); // {}
825
- ```
826
-
827
- You can also provide an optional prefix.
828
814
 
829
- ```typescript
830
- import { getParameters } from "@aminnairi/react-router";
831
-
832
- getParameters("/", "/github", "/github"); // {}
833
-
834
- getParameters("/", "/github/about", "/github"); // {}
835
-
836
- getParameters("/users/:user", "/github/users/123", "/github"); // { user: "123" }
837
-
838
- getParameters("/users/:user", "/github/users/123/articles", "/github"); // {}
815
+ const { RouterProvider, RouterView, usePrefix } = createRouter({
816
+ prefix: "/portfolio",
817
+ fallback: () => <h1>Not found</h1>,
818
+ issue: () => <h1>An error occurred</h1>,
819
+ pages: [home],
820
+ });
839
821
  ```
840
822
 
841
- ### sanitizePath
823
+ ### usePath
842
824
 
843
- Internal function that helps normalizing the URL by removing trailing and leading slashes as well as removing any duplicate and unecessary slashes.
825
+ Allow you to get the current path.
844
826
 
845
- ```ts
846
- import { sanitizePath } from "@aminnairi/react-router";
827
+ Note: This hook is returned from `createRouter`, not imported directly from the library.
847
828
 
848
- sanitizePath("/"); // "/"
829
+ ```tsx
830
+ import { Fragment } from "react";
831
+ import { createPage, createRouter } from "@aminnairi/react-router";
849
832
 
850
- sanitizePath("users"); // "/users"
833
+ const home = createPage({
834
+ path: "/",
835
+ element: function Home() {
836
+ const { path } = usePath();
851
837
 
852
- sanitizePath("users/"); // "/users"
838
+ return (
839
+ <Fragment>
840
+ <h1>Home</h1>
841
+ <p>Current path: {path}</p>
842
+ </Fragment>
843
+ );
844
+ },
845
+ });
853
846
 
854
- sanitizePath("users//123///articles"); // "/users/123/articles"
847
+ const { RouterProvider, RouterView, usePath } = createRouter({
848
+ fallback: () => <h1>Not found</h1>,
849
+ issue: () => <h1>An error occurred</h1>,
850
+ pages: [home],
851
+ });
855
852
  ```
856
853
 
857
854
  ## Features
@@ -874,7 +871,7 @@ This means that you can use this library with other popular solutions for handli
874
871
 
875
872
  ### Transition
876
873
 
877
- Support for the View Transition API is built-in and allows for painless and smooth view transition out-of-the-box. You can create your own transition animation, and the library also exports a `slideFadeTransition` ready to be used.
874
+ Support for the View Transition API is built-in and allows for painless and smooth view transition out-of-the-box. You can create your own transition animation, and the library also exports several transitions ready to be used: `slideHorizontalTransition`, `slideVerticalTransition`, `crossFadeTransition`, and `scaleFadeTransition`.
878
875
 
879
876
  ### Error handling
880
877
 
@@ -888,6 +885,7 @@ See [`LICENSE`](./LICENSE).
888
885
 
889
886
  ### Versions
890
887
 
888
+ - [`3.0.0`](#300)
891
889
  - [`2.1.0`](#210)
892
890
  - [`2.0.1`](#201)
893
891
  - [`2.0.0`](#200)
@@ -897,6 +895,31 @@ See [`LICENSE`](./LICENSE).
897
895
  - [`0.1.1`](#011)
898
896
  - [`0.1.0`](#010)
899
897
 
898
+ ### 3.0.0
899
+
900
+ #### Major changes
901
+
902
+ - Full rewrite of the library implementation
903
+ - Added `locales` support for internationalization
904
+ - Added `useLocale` hook for locale management
905
+ - Added `usePrefix` and `usePath` hooks
906
+ - Renamed `slideFadeTransition` to `slideHorizontalTransition` and added new transitions: `scaleFadeTransition`, `crossFadeTransition`, `slideVerticalTransition`
907
+ - Changed API structure: `router.View` → `router.RouterView` and `router.Provider` → `RouterProvider`
908
+ - Renamed `reset` to `resetError` in `IssueProps`
909
+ - Renamed internal functions: `doesRouteMatchPath` → `matchPath`, `getParameters` → `matchParameters`
910
+ - Added `Uri` class for URL parsing
911
+ - Changed `createRouter` return value structure
912
+ - Removed `useLink`, `useSearch`, and `useHash` hooks and `UseLinkRenderFunction` type
913
+
914
+ #### Minor changes
915
+
916
+ - Added better URL normalization with `normalize` function
917
+ - Improved error handling with `ErrorBoundary` component
918
+
919
+ #### Bug & security fixes
920
+
921
+ - None.
922
+
900
923
  ### 2.1.0
901
924
 
902
925
  #### Major changes