@real-router/react 0.12.2 → 0.12.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.
Files changed (2) hide show
  1. package/README.md +93 -243
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,284 +1,123 @@
1
1
  # @real-router/react
2
2
 
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![React](https://img.shields.io/badge/React-19.2+-61DAFB.svg)](https://react.dev/)
3
+ [![npm](https://img.shields.io/npm/v/@real-router/react.svg?style=flat-square)](https://www.npmjs.com/package/@real-router/react)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@real-router/react.svg?style=flat-square)](https://www.npmjs.com/package/@real-router/react)
5
+ [![bundle size](https://deno.bundlejs.com/?q=@real-router/react&treeshake=[*]&badge=detailed)](https://bundlejs.com/?q=@real-router/react&treeshake=[*])
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE)
5
7
 
6
- React integration for Real-Router — hooks, components, and context providers.
8
+ > React integration for [Real-Router](https://github.com/greydragon888/real-router) — hooks, components, and context providers.
7
9
 
8
10
  ## Installation
9
11
 
10
12
  ```bash
11
13
  npm install @real-router/react @real-router/core @real-router/browser-plugin
12
- # or
13
- pnpm add @real-router/react @real-router/core @real-router/browser-plugin
14
- # or
15
- yarn add @real-router/react @real-router/core @real-router/browser-plugin
16
- # or
17
- bun add @real-router/react @real-router/core @real-router/browser-plugin
18
14
  ```
19
15
 
20
- **Peer Dependencies:** `react` >= 18.0.0
16
+ **Peer dependency:** `react` >= 18.0.0
21
17
 
22
18
  ## Entry Points
23
19
 
24
- The package provides two entry points via subpath exports:
20
+ | Import Path | React Version | Includes |
21
+ |-------------|---------------|----------|
22
+ | `@real-router/react` | 19.2+ | Full API (hooks, `Link`, `RouteView` with `keepAlive`) |
23
+ | `@real-router/react/legacy` | 18+ | All hooks and `Link`, no `RouteView` |
25
24
 
26
- | Import Path | React Version | Description |
27
- | --------------------------- | ------------- | ----------------------------------------- |
28
- | `@real-router/react` | 19.2+ | Full API (default) |
29
- | `@real-router/react/legacy` | 18+ | Same API minus React 19.2-only components |
30
-
31
- ```tsx
32
- // React 19.2+ (default) — full API
33
- import { RouterProvider, useRouteNode, Link } from "@real-router/react";
34
-
35
- // React 18+ — without React 19.2-only components
36
- import { RouterProvider, useRouteNode, Link } from "@real-router/react/legacy";
37
- ```
38
-
39
- Both entry points share the same underlying code — `/legacy` is a re-export subset that excludes components requiring React 19.2+ APIs (e.g., `<Activity>`).
25
+ Both share the same underlying code — `/legacy` excludes components that require React 19.2's `<Activity>` API.
40
26
 
41
27
  ## Quick Start
42
28
 
43
29
  ```tsx
44
30
  import { createRouter } from "@real-router/core";
45
31
  import { browserPluginFactory } from "@real-router/browser-plugin";
46
- import { RouterProvider, useRoute, Link } from "@real-router/react";
47
- import { createRoot } from "react-dom/client";
32
+ import { RouterProvider, RouteView, Link } from "@real-router/react";
48
33
 
49
- // Define routes
50
- const routes = [
34
+ const router = createRouter([
51
35
  { name: "home", path: "/" },
52
- {
53
- name: "users",
54
- path: "/users",
55
- children: [{ name: "profile", path: "/:id" }],
56
- },
57
- ];
58
-
59
- // Create and configure router
60
- const router = createRouter(routes);
36
+ { name: "users", path: "/users", children: [
37
+ { name: "profile", path: "/:id" },
38
+ ]},
39
+ ]);
40
+
61
41
  router.usePlugin(browserPluginFactory());
62
42
  router.start();
63
43
 
64
- // App component
65
44
  function App() {
66
- const { route } = useRoute();
67
-
68
45
  return (
69
- <div>
46
+ <RouterProvider router={router}>
70
47
  <nav>
71
48
  <Link routeName="home">Home</Link>
72
49
  <Link routeName="users">Users</Link>
73
50
  </nav>
74
- <main>
75
- <h1>Current route: {route?.name}</h1>
76
- </main>
77
- </div>
51
+ <RouteView nodeName="">
52
+ <RouteView.Match routeName="home"><HomePage /></RouteView.Match>
53
+ <RouteView.Match routeName="users"><UsersPage /></RouteView.Match>
54
+ <RouteView.NotFound><NotFoundPage /></RouteView.NotFound>
55
+ </RouteView>
56
+ </RouterProvider>
78
57
  );
79
58
  }
80
-
81
- createRoot(document.getElementById("root")!).render(
82
- <RouterProvider router={router}>
83
- <App />
84
- </RouterProvider>,
85
- );
86
- ```
87
-
88
- ---
89
-
90
- ## API
91
-
92
- ### Provider
93
-
94
- #### `<RouterProvider router={router}>`
95
-
96
- Provides router instance to component tree via React Context.\
97
- `router: Router` — router instance from `createRouter()`\
98
- `children: ReactNode` — child components\
99
- [Wiki](https://github.com/greydragon888/real-router/wiki/RouterProvider)
100
-
101
- ```tsx
102
- <RouterProvider router={router}>
103
- <App />
104
- </RouterProvider>
105
- ```
106
-
107
- ---
108
-
109
- ### Hooks
110
-
111
- #### `useRouter(): Router`
112
-
113
- Get router instance. **Never re-renders** on navigation.\
114
- Returns: `Router` — router instance\
115
- [Wiki](https://github.com/greydragon888/real-router/wiki/useRouter)
116
-
117
- ```tsx
118
- import { useRouter } from "@real-router/react";
119
-
120
- const NavigateButton = () => {
121
- const router = useRouter();
122
-
123
- return <button onClick={() => router.navigate("home")}>Go Home</button>;
124
- };
125
59
  ```
126
60
 
127
- #### `useRoute(): { router, route, previousRoute }`
61
+ ## Hooks
128
62
 
129
- Get current route state. **Re-renders on every navigation.**\
130
- Returns: `{ router: Router, route: State | undefined, previousRoute: State | undefined }`\
131
- [Wiki](https://github.com/greydragon888/real-router/wiki/useRoute)
63
+ | Hook | Returns | Re-renders |
64
+ |------|---------|------------|
65
+ | `useRouter()` | `Router` | Never |
66
+ | `useNavigator()` | `Navigator` | Never (stable ref, safe to destructure) |
67
+ | `useRoute()` | `{ router, route, previousRoute }` | Every navigation |
68
+ | `useRouteNode(name)` | `{ router, route, previousRoute }` | Only when node activates/deactivates |
69
+ | `useRouteUtils()` | `RouteUtils` | Never |
70
+ | `useRouterTransition()` | `{ isTransitioning, toRoute, fromRoute }` | On transition start/end |
132
71
 
133
72
  ```tsx
134
- import { useRoute } from "@real-router/react";
135
-
136
- const CurrentRoute = () => {
137
- const { router, route, previousRoute } = useRoute();
138
-
139
- return (
140
- <div>
141
- <p>Current: {route?.name}</p>
142
- <p>Previous: {previousRoute?.name}</p>
143
- <p>Params: {JSON.stringify(route?.params)}</p>
144
- <button onClick={() => router.navigate("home")}>Go Home</button>
145
- </div>
146
- );
147
- };
148
- ```
149
-
150
- #### `useRouteNode(nodeName: string): { router, route, previousRoute }`
151
-
152
- Optimized hook for nested routes. **Re-renders only when specified node changes.**\
153
- `nodeName: string` — route segment to observe (e.g., `"users"`)
154
- Returns: `{ router: Router, route: State | undefined, previousRoute: State | undefined }`\
155
- [Wiki](https://github.com/greydragon888/real-router/wiki/useRouteNode)
156
-
157
- ```tsx
158
- import { useRouteNode } from "@real-router/react";
159
-
160
- const UsersSection = () => {
161
- // Only re-renders when routes starting with "users" change
162
- const { router, route, previousRoute } = useRouteNode("users");
163
-
164
- // route is undefined when current route is NOT under "users" node
165
- if (!route) {
166
- return null;
167
- }
73
+ // useRouteNode re-renders only when "users.*" changes
74
+ function UsersLayout() {
75
+ const { route } = useRouteNode("users");
76
+ if (!route) return null;
168
77
 
169
78
  switch (route.name) {
170
- case "users":
171
- return <UsersList />;
172
- case "users.profile":
173
- return <UserProfile id={route.params.id} />;
174
- default:
175
- return null;
79
+ case "users": return <UsersList />;
80
+ case "users.profile": return <UserProfile id={route.params.id} />;
81
+ default: return null;
176
82
  }
177
- };
178
- ```
179
-
180
- #### `useRouteUtils(): RouteUtils`
181
-
182
- Get pre-computed route tree query utilities. **Never re-renders** on navigation.\
183
- Returns: `RouteUtils` — cached instance with chain, sibling, and descendant lookups\
184
- [Wiki](https://github.com/greydragon888/real-router/wiki/useRouteUtils)
185
-
186
- ```tsx
187
- import { useRouteUtils } from "@real-router/react";
188
-
189
- const Breadcrumbs = () => {
190
- const utils = useRouteUtils();
191
- const chain = utils.getChain("users.profile");
192
- // → ["users", "users.profile"]
193
-
194
- return (
195
- <nav>
196
- {chain?.map((segment) => (
197
- <span key={segment}>{segment}</span>
198
- ))}
199
- </nav>
200
- );
201
- };
202
- ```
203
-
204
- #### `useRouterTransition(): RouterTransitionSnapshot`
205
-
206
- Track router transition lifecycle (start/success/error/cancel). **Re-renders on transition start and end.**\
207
- Returns: `RouterTransitionSnapshot` — `{ isTransitioning: boolean, toRoute: State | null, fromRoute: State | null }`
208
-
209
- ```tsx
210
- import { useRouterTransition } from "@real-router/react";
83
+ }
211
84
 
212
- const GlobalProgress = () => {
213
- const { isTransitioning, toRoute, fromRoute } = useRouterTransition();
85
+ // useNavigator stable reference, never causes re-renders
86
+ function BackButton() {
87
+ const navigator = useNavigator();
88
+ return <button onClick={() => navigator.navigate("home")}>Back</button>;
89
+ }
214
90
 
91
+ // useRouterTransition — progress bars, loading states
92
+ function GlobalProgress() {
93
+ const { isTransitioning } = useRouterTransition();
215
94
  if (!isTransitioning) return null;
216
-
217
- return (
218
- <div className="progress-bar">
219
- Navigating from {fromRoute?.name} to {toRoute?.name}…
220
- </div>
221
- );
222
- };
95
+ return <div className="progress-bar" />;
96
+ }
223
97
  ```
224
98
 
225
- Useful for progress bars, loading overlays, and disabling UI during async navigation guards. Returns `{ isTransitioning: false, toRoute: null, fromRoute: null }` when idle.
226
-
227
- ---
99
+ ## Components
228
100
 
229
- ### Components
101
+ ### `<Link>`
230
102
 
231
- #### `<Link routeName={string} routeParams={object} ...props>`
232
-
233
- Navigation link with automatic active state detection. Re-renders only when its own active status changes.\
234
- `routeName: string` — target route name\
235
- `routeParams?: Params` — route parameters\
236
- `routeOptions?: { reload?, replace? }` — navigation options\
237
- `activeClassName?: string` — class when active (default: `"active"`)
238
- `activeStrict?: boolean` — exact match only (default: `false`)
239
- `ignoreQueryParams?: boolean` — ignore query params in active check (default: `true`)\
240
- [Wiki](https://github.com/greydragon888/real-router/wiki/Link)
103
+ Navigation link with automatic active state detection. Re-renders only when its active status changes.
241
104
 
242
105
  ```tsx
243
- import { Link } from "@real-router/react";
244
-
245
106
  <Link
246
107
  routeName="users.profile"
247
108
  routeParams={{ id: "123" }}
248
- activeClassName="active"
249
- activeStrict={false}
109
+ activeClassName="active" // default: "active"
110
+ activeStrict={false} // default: false (ancestor match)
111
+ ignoreQueryParams={true} // default: true
112
+ routeOptions={{ replace: true }}
250
113
  >
251
114
  View Profile
252
- </Link>;
253
- ```
254
-
255
- #### `<RouteView nodeName={string}>`
256
-
257
- Declarative route matching component. Subscribes to a route node and renders the first matched segment.\
258
- `nodeName: string` — route node to subscribe to (`""` for root)\
259
- [Wiki](https://github.com/greydragon888/real-router/wiki/RouteView)
260
-
261
- > **Note:** `RouteView` is only available from the main entry point (`@real-router/react`). It requires React 19.2+ for `keepAlive` support via `<Activity>`.
262
-
263
- ```tsx
264
- import { RouteView } from "@real-router/react";
265
-
266
- <RouteView nodeName="">
267
- <RouteView.Match segment="users">
268
- <UsersPage />
269
- </RouteView.Match>
270
- <RouteView.Match segment="settings">
271
- <SettingsPage />
272
- </RouteView.Match>
273
- <RouteView.NotFound>
274
- <NotFoundPage />
275
- </RouteView.NotFound>
276
- </RouteView>;
115
+ </Link>
277
116
  ```
278
117
 
279
- ##### `keepAlive`
118
+ ### `<RouteView>` (React 19.2+)
280
119
 
281
- `<RouteView.Match>` accepts an optional `keepAlive` prop. When enabled, the matched component's state and DOM are preserved when navigating away, using React 19.2's `<Activity>` API.
120
+ Declarative route matching with optional `keepAlive` preserves component state via React's `<Activity>` API.
282
121
 
283
122
  ```tsx
284
123
  <RouteView nodeName="">
@@ -288,43 +127,54 @@ import { RouteView } from "@real-router/react";
288
127
  <RouteView.Match segment="settings">
289
128
  <SettingsPage /> {/* Unmounts normally */}
290
129
  </RouteView.Match>
130
+ <RouteView.NotFound>
131
+ <NotFoundPage />
132
+ </RouteView.NotFound>
291
133
  </RouteView>
292
134
  ```
293
135
 
294
- When the user navigates away from a `keepAlive` match, the component is hidden (via `<Activity mode="hidden">`) rather than unmounted. Navigating back restores the previous state instantly without re-mounting.
295
-
296
- ---
297
-
298
- ## Migration from React 18
136
+ ## React 18 Migration
299
137
 
300
- If your app uses React 18 (or < 19.2), use the legacy entry point:
138
+ One import path change all hooks and `Link` work identically:
301
139
 
302
140
  ```diff
303
141
  - import { useRouteNode, Link } from '@real-router/react';
304
142
  + import { useRouteNode, Link } from '@real-router/react/legacy';
305
143
  ```
306
144
 
307
- One import path change. All hooks and `Link` work identically. The only difference: `RouteView` (which uses React 19.2's `<Activity>` for `keepAlive`) is not available from `/legacy`. Use `useRouteNode` with a switch/case pattern instead.
308
-
309
- ---
145
+ `RouteView` is not available from `/legacy`. Use `useRouteNode` with a switch/case pattern instead.
310
146
 
311
147
  ## Migration from react-router5
312
148
 
313
- | API | react-router5 | @real-router/react |
314
- | --------------------------------------------- | ------------- | ------------------ |
315
- | `RouterProvider` | | |
316
- | `Link` | | |
317
- | `useRouter`, `useRoute`, `useRouteNode` | | |
318
- | `withRouter`, `withRoute`, `routeNode` | | Use hooks |
319
- | `Router`, `Route`, `RouteNode` (render props) | | Use hooks |
149
+ | API | react-router5 | @real-router/react |
150
+ |-----|---------------|-------------------|
151
+ | `RouterProvider`, `Link` | Yes | Yes |
152
+ | `useRouter`, `useRoute`, `useRouteNode` | Yes | Yes |
153
+ | `RouteView` with `keepAlive` | No | Yes (React 19.2+) |
154
+ | `useNavigator`, `useRouteUtils`, `useRouterTransition` | No | Yes |
155
+ | `withRouter`, `withRoute`, `routeNode` (HOCs) | Yes | No use hooks |
156
+ | `Router`, `Route`, `RouteNode` (render props) | Yes | No — use hooks |
157
+
158
+ ## Documentation
159
+
160
+ Full documentation: [Wiki](https://github.com/greydragon888/real-router/wiki)
320
161
 
321
- ---
162
+ - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [Link](https://github.com/greydragon888/real-router/wiki/Link)
163
+ - [useRouter](https://github.com/greydragon888/real-router/wiki/useRouter) · [useRoute](https://github.com/greydragon888/real-router/wiki/useRoute) · [useRouteNode](https://github.com/greydragon888/real-router/wiki/useRouteNode) · [useNavigator](https://github.com/greydragon888/real-router/wiki/useNavigator) · [useRouteUtils](https://github.com/greydragon888/real-router/wiki/useRouteUtils) · [useRouterTransition](https://github.com/greydragon888/real-router/wiki/useRouterTransition)
322
164
 
323
165
  ## Related Packages
324
166
 
325
- - [@real-router/core](https://www.npmjs.com/package/@real-router/core) Core router
326
- - [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) — Browser history
167
+ | Package | Description |
168
+ |---------|-------------|
169
+ | [@real-router/core](https://www.npmjs.com/package/@real-router/core) | Core router (required dependency) |
170
+ | [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) | Browser History API integration |
171
+ | [@real-router/sources](https://www.npmjs.com/package/@real-router/sources) | Subscription layer (used internally) |
172
+ | [@real-router/route-utils](https://www.npmjs.com/package/@real-router/route-utils) | Route tree queries (`useRouteUtils`) |
173
+
174
+ ## Contributing
175
+
176
+ See [contributing guidelines](../../CONTRIBUTING.md) for development setup and PR process.
327
177
 
328
178
  ## License
329
179
 
330
- MIT © [Oleg Ivanov](https://github.com/greydragon888)
180
+ [MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/react",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "type": "commonjs",
5
5
  "description": "React integration for Real-Router",
6
6
  "main": "./dist/cjs/index.js",
@@ -64,9 +64,9 @@
64
64
  "license": "MIT",
65
65
  "sideEffects": false,
66
66
  "dependencies": {
67
- "@real-router/core": "^0.36.0",
68
- "@real-router/sources": "^0.2.3",
69
- "@real-router/route-utils": "^0.1.4"
67
+ "@real-router/core": "^0.37.0",
68
+ "@real-router/route-utils": "^0.1.5",
69
+ "@real-router/sources": "^0.2.4"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@testing-library/dom": "10.4.1",
@@ -74,7 +74,7 @@
74
74
  "@testing-library/react": "16.3.2",
75
75
  "@testing-library/user-event": "14.6.1",
76
76
  "vitest-react-profiler": "1.12.0",
77
- "@real-router/browser-plugin": "^0.10.1"
77
+ "@real-router/browser-plugin": "^0.10.2"
78
78
  },
79
79
  "peerDependencies": {
80
80
  "@types/react": ">=18.0.0",