@solidjs/router 0.15.3 → 0.15.4

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
@@ -1,16 +1,34 @@
1
- <p>
2
- <img src="https://assets.solidjs.com/banner?project=Router&type=core" alt="Solid Router" />
3
- </p>
1
+ [![Banner](https://assets.solidjs.com/banner?project=Router&type=core)](https://github.com/solidjs)
4
2
 
5
- # Solid Router [![npm Version](https://img.shields.io/npm/v/@solidjs/router.svg?style=flat-square)](https://www.npmjs.org/package/@solidjs/router)
3
+ <div align="center">
6
4
 
7
- **Version 0.10.0 requires Solid v1.8.4 or later.**
5
+ [![Version](https://img.shields.io/npm/v/@solidjs/router.svg?style=for-the-badge&color=blue&logo=npm)](https://npmjs.com/package/@solidjs/router)
6
+ [![Downloads](https://img.shields.io/npm/dm/@solidjs/router.svg?style=for-the-badge&color=green&logo=npm)](https://npmjs.com/package/@solidjs/router)
7
+ [![Stars](https://img.shields.io/github/stars/solidjs/solid-router?style=for-the-badge&color=yellow&logo=github)](https://github.com/solidjs/solid-router)
8
+ [![Discord](https://img.shields.io/discord/722131463138705510?label=join&style=for-the-badge&color=5865F2&logo=discord&logoColor=white)](https://discord.com/invite/solidjs)
9
+ [![Reddit](https://img.shields.io/reddit/subreddit-subscribers/solidjs?label=join&style=for-the-badge&color=FF4500&logo=reddit&logoColor=white)](https://reddit.com/r/solidjs)
8
10
 
9
- A router lets you change your view based on the URL in the browser. This allows your "single-page" application to simulate a traditional multipage site. To use Solid Router, you specify components called Routes that depend on the value of the URL (the "path"), and the router handles the mechanism of swapping them in and out.
11
+ </div>
10
12
 
11
- Solid Router is a universal router for SolidJS - it works whether you're rendering on the client or on the server. It was inspired by and combines paradigms of React Router and the Ember Router. Routes can be defined directly in your app's template using JSX, but you can also pass your route configuration directly as an object. It also supports nested routing, so navigation can change a part of a component, rather than completely replacing it.
13
+ **Solid Router** brings fine-grained reactivity to route navigation, enabling your single-page application to become multi-paged without full page reloads. Fully integrated into the SolidJS ecosystem, Solid Router provides declarative syntax with features like universal rendering and parallel data fetching for best performance.
12
14
 
13
- It supports all of Solid's SSR methods and has Solid's transitions baked in, so use it freely with suspense, resources, and lazy components. Solid Router also allows you to define a preload function that loads parallel to the routes ([render-as-you-fetch](https://epicreact.dev/render-as-you-fetch/)).
15
+ Explore the official [documentation](https://docs.solidjs.com/solid-router) for detailed guides and examples.
16
+
17
+ ## Core Features
18
+
19
+ - **All Routing Modes**:
20
+ - [History-Based](https://docs.solidjs.com/solid-router/reference/components/router#router) for standard browser navigation
21
+ - [Hash-Based](https://docs.solidjs.com/solid-router/reference/components/hash-router#hashrouter) for navigation based on URL hash
22
+ - [Static Routing](https://docs.solidjs.com/solid-router/rendering-modes/ssr#server-side-rendering) for server-side rendering (_SSR_)
23
+ - [Memory-Based](https://docs.solidjs.com/solid-router/reference/components/memory-router#memoryrouter) for testing in non-browser environments
24
+ - **TypeScript**: Full integration for robust, type-safe development
25
+ - **Universal Rendering**: Seamless rendering on both client and server environments
26
+ - **Declarative**: Define routes as components or as an object
27
+ - **Preload Functions**: Parallel data fetching, following the render-as-you-fetch pattern
28
+ - **Dynamic Route Parameters**: Flexible URL patterns with parameters, optional segments, and wildcards
29
+ - **Data APIs with Caching**: Reactive data fetching with deduplication and revalidation
30
+
31
+ ## Table of contents
14
32
 
15
33
  - [Getting Started](#getting-started)
16
34
  - [Set Up the Router](#set-up-the-router)
@@ -38,8 +56,9 @@ It supports all of Solid's SSR methods and has Solid's transitions baked in, so
38
56
 
39
57
  ### Set Up the Router
40
58
 
41
- ```sh
42
- > npm i @solidjs/router
59
+ ```bash
60
+ # use preferred package manager
61
+ npm add @solidjs/router
43
62
  ```
44
63
 
45
64
  Install `@solidjs/router`, then start your application by rendering the router component
@@ -48,10 +67,7 @@ Install `@solidjs/router`, then start your application by rendering the router c
48
67
  import { render } from "solid-js/web";
49
68
  import { Router } from "@solidjs/router";
50
69
 
51
- render(
52
- () => <Router />,
53
- document.getElementById("app")
54
- );
70
+ render(() => <Router />, document.getElementById("app"));
55
71
  ```
56
72
 
57
73
  This sets up a Router that will match on the url to display the desired page
@@ -69,13 +85,17 @@ import { Router, Route } from "@solidjs/router";
69
85
  import Home from "./pages/Home";
70
86
  import Users from "./pages/Users";
71
87
 
72
- render(() => (
73
- <Router>
74
- <Route path="/users" component={Users} />
75
- <Route path="/" component={Home} />
76
- </Router>
77
- ), document.getElementById("app"));
88
+ render(
89
+ () => (
90
+ <Router>
91
+ <Route path="/users" component={Users} />
92
+ <Route path="/" component={Home} />
93
+ </Router>
94
+ ),
95
+ document.getElementById("app")
96
+ );
78
97
  ```
98
+
79
99
  2. Provide a root level layout
80
100
 
81
101
  This will always be there and won't update on page change. It is the ideal place to put top level navigation and Context Providers
@@ -87,24 +107,27 @@ import { Router, Route } from "@solidjs/router";
87
107
  import Home from "./pages/Home";
88
108
  import Users from "./pages/Users";
89
109
 
90
- const App = props => (
110
+ const App = (props) => (
91
111
  <>
92
112
  <h1>My Site with lots of pages</h1>
93
113
  {props.children}
94
114
  </>
95
- )
115
+ );
96
116
 
97
- render(() => (
98
- <Router root={App}>
99
- <Route path="/users" component={Users} />
100
- <Route path="/" component={Home} />
101
- </Router>
102
- ), document.getElementById("app"));
117
+ render(
118
+ () => (
119
+ <Router root={App}>
120
+ <Route path="/users" component={Users} />
121
+ <Route path="/" component={Home} />
122
+ </Router>
123
+ ),
124
+ document.getElementById("app")
125
+ );
103
126
  ```
104
127
 
105
- 3. Create a CatchAll Route (404 page)
128
+ 3. Create a catch-all route (404 page)
106
129
 
107
- We can create catchall routes for pages not found at any nested level of the router. We use `*` and optionally the name of a parameter to retrieve the rest of the path.
130
+ We can create catch-all routes for pages not found at any nested level of the router. We use `*` and optionally the name of a parameter to retrieve the rest of the path.
108
131
 
109
132
  ```jsx
110
133
  import { render } from "solid-js/web";
@@ -114,20 +137,23 @@ import Home from "./pages/Home";
114
137
  import Users from "./pages/Users";
115
138
  import NotFound from "./pages/404";
116
139
 
117
- const App = props => (
140
+ const App = (props) => (
118
141
  <>
119
142
  <h1>My Site with lots of pages</h1>
120
143
  {props.children}
121
144
  </>
122
- )
145
+ );
123
146
 
124
- render(() => (
125
- <Router root={App}>
126
- <Route path="/users" component={Users} />
127
- <Route path="/" component={Home} />
128
- <Route path="*404" component={NotFound} />
129
- </Router>
130
- ), document.getElementById("app"));
147
+ render(
148
+ () => (
149
+ <Router root={App}>
150
+ <Route path="/users" component={Users} />
151
+ <Route path="/" component={Home} />
152
+ <Route path="*404" component={NotFound} />
153
+ </Router>
154
+ ),
155
+ document.getElementById("app")
156
+ );
131
157
  ```
132
158
 
133
159
  4. Lazy-load route components
@@ -142,19 +168,22 @@ import { Router, Route } from "@solidjs/router";
142
168
  const Users = lazy(() => import("./pages/Users"));
143
169
  const Home = lazy(() => import("./pages/Home"));
144
170
 
145
- const App = props => (
171
+ const App = (props) => (
146
172
  <>
147
173
  <h1>My Site with lots of pages</h1>
148
174
  {props.children}
149
175
  </>
150
- )
176
+ );
151
177
 
152
- render(() => (
153
- <Router root={App}>
154
- <Route path="/users" component={Users} />
155
- <Route path="/" component={Home} />
156
- </Router>
157
- ), document.getElementById("app"));
178
+ render(
179
+ () => (
180
+ <Router root={App}>
181
+ <Route path="/users" component={Users} />
182
+ <Route path="/" component={Home} />
183
+ </Router>
184
+ ),
185
+ document.getElementById("app")
186
+ );
158
187
  ```
159
188
 
160
189
  ### Create Links to Your Routes
@@ -169,7 +198,7 @@ import { Router, Route } from "@solidjs/router";
169
198
  const Users = lazy(() => import("./pages/Users"));
170
199
  const Home = lazy(() => import("./pages/Home"));
171
200
 
172
- const App = props => (
201
+ const App = (props) => (
173
202
  <>
174
203
  <nav>
175
204
  <a href="/about">About</a>
@@ -180,12 +209,15 @@ const App = props => (
180
209
  </>
181
210
  );
182
211
 
183
- render(() => (
184
- <Router root={App}>
185
- <Route path="/users" component={Users} />
186
- <Route path="/" component={Home} />
187
- </Router>
188
- ), document.getElementById("app"));
212
+ render(
213
+ () => (
214
+ <Router root={App}>
215
+ <Route path="/users" component={Users} />
216
+ <Route path="/" component={Home} />
217
+ </Router>
218
+ ),
219
+ document.getElementById("app")
220
+ );
189
221
  ```
190
222
 
191
223
  ## Dynamic Routes
@@ -201,13 +233,16 @@ const Users = lazy(() => import("./pages/Users"));
201
233
  const User = lazy(() => import("./pages/User"));
202
234
  const Home = lazy(() => import("./pages/Home"));
203
235
 
204
- render(() => (
205
- <Router>
206
- <Route path="/users" component={Users} />
207
- <Route path="/users/:id" component={User} />
208
- <Route path="/" component={Home} />
209
- </Router>
210
- ), document.getElementById("app"));
236
+ render(
237
+ () => (
238
+ <Router>
239
+ <Route path="/users" component={Users} />
240
+ <Route path="/users/:id" component={User} />
241
+ <Route path="/" component={Home} />
242
+ </Router>
243
+ ),
244
+ document.getElementById("app")
245
+ );
211
246
  ```
212
247
 
213
248
  The colon indicates that `id` can be any string, and as long as the URL fits that pattern, the `User` component will show.
@@ -216,9 +251,13 @@ You can then access that `id` from within a route component with `useParams`.
216
251
 
217
252
  **Note on Animation/Transitions**:
218
253
  Routes that share the same path match will be treated as the same route. If you want to force re-render you can wrap your component in a keyed `<Show>` like:
254
+
219
255
  ```jsx
220
- <Show when={params.something} keyed><MyComponent></Show>
256
+ <Show when={params.something} keyed>
257
+ <MyComponent />
258
+ </Show>
221
259
  ```
260
+
222
261
  ---
223
262
 
224
263
  Each path parameter can be validated using a `MatchFilter`.
@@ -228,7 +267,7 @@ This allows for more complex routing descriptions than just checking the presenc
228
267
  import { lazy } from "solid-js";
229
268
  import { render } from "solid-js/web";
230
269
  import { Router, Route } from "@solidjs/router";
231
- import type { SegmentValidators } from "./types";
270
+ import type { MatchFilters } from "@solidjs/router";
232
271
 
233
272
  const User = lazy(() => import("./pages/User"));
234
273
 
@@ -238,15 +277,18 @@ const filters: MatchFilters = {
238
277
  withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html"), // we want an `*.html` extension
239
278
  };
240
279
 
241
- render(() => (
242
- <Router>
243
- <Route
244
- path="/users/:parent/:id/:withHtmlExtension"
245
- component={User}
246
- matchFilters={filters}
247
- />
248
- </Router>
249
- ), document.getElementById("app"));
280
+ render(
281
+ () => (
282
+ <Router>
283
+ <Route
284
+ path="/users/:parent/:id/:withHtmlExtension"
285
+ component={User}
286
+ matchFilters={filters}
287
+ />
288
+ </Router>
289
+ ),
290
+ document.getElementById("app")
291
+ );
250
292
  ```
251
293
 
252
294
  Here, we have added the `matchFilters` prop. This allows us to validate the `parent`, `id` and `withHtmlExtension` parameters against the filters defined in `filters`.
@@ -358,24 +400,13 @@ You can nest indefinitely - just remember that only leaf nodes will become their
358
400
  ```jsx
359
401
  <Route
360
402
  path="/"
361
- component={(props) =>
362
- <div>
363
- Onion starts here {props.children}
364
- </div>
365
- }
403
+ component={(props) => <div>Onion starts here {props.children}</div>}
366
404
  >
367
405
  <Route
368
406
  path="layer1"
369
- component={(props) =>
370
- <div>
371
- Another layer {props.children}
372
- </div>
373
- }
407
+ component={(props) => <div>Another layer {props.children}</div>}
374
408
  >
375
- <Route
376
- path="layer2"
377
- component={() => <div>Innermost layer</div>}
378
- />
409
+ <Route path="layer2" component={() => <div>Innermost layer</div>} />
379
410
  </Route>
380
411
  </Route>
381
412
  ```
@@ -393,7 +424,7 @@ import { Route } from "@solidjs/router";
393
424
  const User = lazy(() => import("./pages/users/[id].js"));
394
425
 
395
426
  // preload function
396
- function preloadUser({params, location}) {
427
+ function preloadUser({ params, location }) {
397
428
  // do preloading
398
429
  }
399
430
 
@@ -401,12 +432,11 @@ function preloadUser({params, location}) {
401
432
  <Route path="/users/:id" component={User} preload={preloadUser} />;
402
433
  ```
403
434
 
404
- | key | type | description |
405
- | -------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
406
- | params | object | The route parameters (same value as calling `useParams()` inside the route component) |
407
- | location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
408
- | intent | `"initial", "navigate", "native", "preload"` | Indicates why this function is being called. <ul><li>"initial" - the route is being initially shown (ie page load)</li><li>"native" - navigate originated from the browser (eg back/forward)</li><li>"navigate" - navigate originated from the router (eg call to navigate or anchor clicked)</li><li>"preload" - not navigating, just preloading (eg link hover)</li></ul> |
409
-
435
+ | key | type | description |
436
+ | -------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
437
+ | params | object | The route parameters (same value as calling `useParams()` inside the route component) |
438
+ | location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
439
+ | intent | `"initial", "navigate", "native", "preload"` | Indicates why this function is being called. <ul><li>"initial" - the route is being initially shown (ie page load)</li><li>"native" - navigate originated from the browser (eg back/forward)</li><li>"navigate" - navigate originated from the router (eg call to navigate or anchor clicked)</li><li>"preload" - not navigating, just preloading (eg link hover)</li></ul> |
410
440
 
411
441
  A common pattern is to export the preload function and data wrappers that corresponds to a route in a dedicated `route.data.js` file. This way, the data function can be imported without loading anything else.
412
442
 
@@ -420,22 +450,22 @@ const User = lazy(() => import("/pages/users/[id].js"));
420
450
  <Route path="/users/:id" component={User} preload={preloadUser} />;
421
451
  ```
422
452
 
423
- The return value of the `preload` function is passed to the page component when called at anytime other than `"preload"` intent, so you can initialize things in there, or alternatively use our new Data APIs:
424
-
453
+ The `preload` function's return value is passed to the page component for any intent other than `"preload"`, allowing you to initialize data or alternatively use our new Data APIs:
425
454
 
426
455
  ## Data APIs
427
456
 
428
- Keep in mind these are completely optional. To use but showcase the power of our preload mechanism.
457
+ Keep in mind that these are entirely optional, but they demonstrate the power of our preload mechanism.
429
458
 
430
459
  ### `query`
431
460
 
432
- To prevent duplicate fetching and to trigger handle refetching we provide a query api. That takes a function and returns the same function.
461
+ To prevent duplicate fetching and to handle refetching triggers, we provide a query API that accepts a function and returns the same function.
433
462
 
434
463
  ```jsx
435
464
  const getUser = query(async (id) => {
436
- return (await fetch(`/api/users${id}`)).json()
437
- }, "users") // used as the query key + serialized arguments
465
+ return (await fetch(`/api/users/${id}`)).json();
466
+ }, "users"); // used as the query key + serialized arguments
438
467
  ```
468
+
439
469
  It is expected that the arguments to the query function are serializable.
440
470
 
441
471
  This query accomplishes the following:
@@ -476,11 +506,12 @@ export default function User(props) {
476
506
  ```
477
507
 
478
508
  Cached function has a few useful methods for getting the key that are useful for invalidation.
509
+
479
510
  ```ts
480
511
  let id = 5;
481
512
 
482
- getUser.key // returns "users"
483
- getUser.keyFor(id) // returns "users[5]"
513
+ getUser.key; // returns "users"
514
+ getUser.keyFor(id); // returns "users[5]"
484
515
  ```
485
516
 
486
517
  You can revalidate the query using the `revalidate` method or you can set `revalidate` keys on your response from your actions. If you pass the whole key it will invalidate all the entries for the query (ie "users" in the example above). You can also invalidate a single entry by using `keyFor`.
@@ -492,13 +523,13 @@ You can revalidate the query using the `revalidate` method or you can set `reval
492
523
  This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it is ready causes Suspense/Transitions to trigger.
493
524
 
494
525
  ```jsx
495
- const user = createAsync((currentValue) => getUser(params.id))
526
+ const user = createAsync((currentValue) => getUser(params.id));
496
527
  ```
497
528
 
498
529
  It also preserves `latest` field from `createResource`. Note that it will be removed in the future.
499
530
 
500
531
  ```jsx
501
- const user = createAsync((currentValue) => getUser(params.id))
532
+ const user = createAsync((currentValue) => getUser(params.id));
502
533
  return <h1>{user.latest.name}</h1>;
503
534
  ```
504
535
 
@@ -516,6 +547,7 @@ const todos = createAsyncStore(() => getTodos());
516
547
  ### `action`
517
548
 
518
549
  Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
550
+
519
551
  ```jsx
520
552
  import { action, revalidate, redirect } from "@solidjs/router"
521
553
 
@@ -549,18 +581,21 @@ const deleteTodo = action(async (formData: FormData) => {
549
581
  <button type="submit">Delete</button>
550
582
  </form>
551
583
  ```
584
+
552
585
  Instead with `with` you can write this:
586
+
553
587
  ```js
554
- const deleteUser = action(api.deleteTodo)
588
+ const deleteTodo = action(api.deleteTodo)
555
589
 
556
590
  <form action={deleteTodo.with(todo.id)} method="post">
557
591
  <button type="submit">Delete</button>
558
592
  </form>
559
593
  ```
560
594
 
561
- Actions also a second argument which can be the name or an option object with `name` and `onComplete`. `name` is used to identify SSR actions that aren't server functions (see note below). `onComplete` allows you to configure behavior when `action`s complete. Keep in mind `onComplete` does not work when JavaScript is disabled.
595
+ Actions also take a second argument which can be the name or an option object with `name` and `onComplete`. `name` is used to identify SSR actions that aren't server functions (see note below). `onComplete` allows you to configure behavior when `action`s complete. Keep in mind `onComplete` does not work when JavaScript is disabled.
562
596
 
563
597
  #### Notes on `<form>` implementation and SSR
598
+
564
599
  This requires stable references as you can only serialize a string as an attribute, and across SSR they'd need to match. The solution is providing a unique name.
565
600
 
566
601
  ```jsx
@@ -573,11 +608,11 @@ Instead of forms you can use actions directly by wrapping them in a `useAction`
573
608
 
574
609
  ```jsx
575
610
  // in component
576
- const submit = useAction(myAction)
577
- submit(...args)
611
+ const submit = useAction(myAction);
612
+ submit(...args);
578
613
  ```
579
614
 
580
- The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhancible like forms are.
615
+ The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhanceable like forms are.
581
616
 
582
617
  ### `useSubmission`/`useSubmissions`
583
618
 
@@ -604,6 +639,7 @@ These are used to communicate router navigations from query/actions, and can inc
604
639
  #### `redirect(path, options)`
605
640
 
606
641
  Redirects to the next route
642
+
607
643
  ```js
608
644
  const getUser = query(() => {
609
645
  const user = await api.getCurrentUser()
@@ -615,16 +651,17 @@ const getUser = query(() => {
615
651
  #### `reload(options)`
616
652
 
617
653
  Reloads the data on the current page
654
+
618
655
  ```js
619
656
  const getTodo = query(async (id: number) => {
620
657
  const todo = await fetchTodo(id);
621
658
  return todo;
622
- }, "todo")
659
+ }, "todo");
623
660
 
624
661
  const updateTodo = action(async (todo: Todo) => {
625
662
  await updateTodo(todo.id, todo);
626
- reload({ revalidate: getTodo.keyFor(id) })
627
- })
663
+ reload({ revalidate: getTodo.keyFor(todo.id) });
664
+ });
628
665
  ```
629
666
 
630
667
  ## Config Based Routing
@@ -669,10 +706,7 @@ const routes = [
669
706
  },
670
707
  ];
671
708
 
672
- render(() =>
673
- <Router>{routes}</Router>,
674
- document.getElementById("app")
675
- );
709
+ render(() => <Router>{routes}</Router>, document.getElementById("app"));
676
710
  ```
677
711
 
678
712
  Also you can pass a single route definition object for a single route:
@@ -684,7 +718,7 @@ import { Router } from "@solidjs/router";
684
718
 
685
719
  const route = {
686
720
  path: "/",
687
- component: lazy(() => import("/pages/index.js"))
721
+ component: lazy(() => import("/pages/index.js")),
688
722
  };
689
723
 
690
724
  render(() => <Router>{route}</Router>, document.getElementById("app"));
@@ -723,29 +757,28 @@ import { Router } from "@solidjs/router";
723
757
  <Router url={isServer ? req.url : ""} />;
724
758
  ```
725
759
 
726
-
727
760
  ## Components
728
761
 
729
762
  ### `<Router>`
730
763
 
731
764
  This is the main Router component for the browser.
732
765
 
733
- | prop | type | description |
734
- |-----|----|----|
735
- | children | `JSX.Element`, `RouteDefinition`, or `RouteDefinition[]` | The route definitions |
736
- | root | Component | Top level layout component |
737
- | base | string | Base url to use for matching routes |
738
- | actionBase | string | Root url for server actions, default: `/_server` |
739
- | preload | boolean | Enables/disables preloads globally, default: `true` |
740
- | explicitLinks | boolean | Disables all anchors being intercepted and instead requires `<A>`. Default: `false`. (To disable interception for a specific link, set `target` to any value, e.g. `<a target="_self">`.) |
766
+ | prop | type | description |
767
+ | ------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
768
+ | children | `JSX.Element`, `RouteDefinition`, or `RouteDefinition[]` | The route definitions |
769
+ | root | Component | Top level layout component |
770
+ | base | string | Base url to use for matching routes |
771
+ | actionBase | string | Root url for server actions, default: `/_server` |
772
+ | preload | boolean | Enables/disables preloads globally, default: `true` |
773
+ | explicitLinks | boolean | Disables all anchors being intercepted and instead requires `<A>`. Default: `false`. (To disable interception for a specific link, set `target` to any value, e.g. `<a target="_self">`.) |
741
774
 
742
775
  ### `<A>`
743
776
 
744
777
  Like the `<a>` tag but supports automatic apply of base path + relative paths and active class styling (requires client side JavaScript).
745
778
 
746
- The `<A>` tag has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything.
779
+ The `<A>` tag has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendants (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything.
747
780
 
748
- | prop | type | description |
781
+ | prop | type | description |
749
782
  | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
750
783
  | href | string | The path of the route to navigate to. This will be resolved relative to the route that the link is in, but you can preface it with `/` to refer back to the root. |
751
784
  | noScroll | boolean | If true, turn off the default behavior of scrolling to the top of the new page |
@@ -753,7 +786,7 @@ The `<A>` tag has an `active` class if its href matches the current location, an
753
786
  | state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating |
754
787
  | inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) |
755
788
  | activeClass | string | The class to show when the link is active |
756
- | end | boolean | If `true`, only considers the link to be active when the curent location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` |
789
+ | end | boolean | If `true`, only considers the link to be active when the current location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` |
757
790
 
758
791
  ### `<Navigate />`
759
792
 
@@ -774,13 +807,13 @@ function getPath({ navigate, location }) {
774
807
 
775
808
  The Component for defining Routes:
776
809
 
777
- | prop | type | description |
778
- |-|-|-|
779
- | path | string | Path partial for defining the route segment |
780
- | component | `Component` | Component that will be rendered for the matched segment |
781
- | matchFilters | `MatchFilters` | Additional constraints for matching against the route |
782
- | children | `JSX.Element` | Nested `<Route>` definitions |
783
- | preload | `RoutePreloadFunc` | Function called during preload or when the route is navigated to. |
810
+ | prop | type | description |
811
+ | ------------ | ------------------ | ----------------------------------------------------------------- |
812
+ | path | string | Path partial for defining the route segment |
813
+ | component | `Component` | Component that will be rendered for the matched segment |
814
+ | matchFilters | `MatchFilters` | Additional constraints for matching against the route |
815
+ | children | `JSX.Element` | Nested `<Route>` definitions |
816
+ | preload | `RoutePreloadFunc` | Function called during preload or when the route is navigated to. |
784
817
 
785
818
  ## Router Primitives
786
819
 
@@ -878,10 +911,13 @@ return <div classList={{ active: Boolean(match()) }} />;
878
911
  `useCurrentMatches` returns all the matches for the current matched route. Useful for getting all the route information.
879
912
 
880
913
  For example if you stored breadcrumbs on your route definition you could retrieve them like so:
914
+
881
915
  ```js
882
916
  const matches = useCurrentMatches();
883
917
 
884
- const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb));
918
+ const breadcrumbs = createMemo(() =>
919
+ matches().map((m) => m.route.info.breadcrumb)
920
+ );
885
921
  ```
886
922
 
887
923
  ### usePreloadRoute
@@ -940,7 +976,7 @@ Related without Outlet component it has to be passed in manually. At which point
940
976
 
941
977
  ### `data` functions & `useRouteData`
942
978
 
943
- These have been replaced by a preload mechanism. This allows link hover preloads (as the preload function can be run as much as wanted without worry about reactivity). It support deduping/query APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
979
+ These have been replaced by a preload mechanism. This allows link hover preloads (as the preload function can be run as much as wanted without worry about reactivity). It support deduping/query APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
944
980
 
945
981
  That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
946
982
 
@@ -951,7 +987,7 @@ import { Route } from "@solidjs/router";
951
987
  const User = lazy(() => import("./pages/users/[id].js"));
952
988
 
953
989
  // preload function
954
- function preloadUser({params, location}) {
990
+ function preloadUser({ params, location }) {
955
991
  const [user] = createResource(() => params.id, fetchUser);
956
992
  return user;
957
993
  }
@@ -959,20 +995,21 @@ function preloadUser({params, location}) {
959
995
  // Pass it in the route definition
960
996
  <Router preload={false}>
961
997
  <Route path="/users/:id" component={User} preload={preloadUser} />
962
- </Router>
998
+ </Router>;
963
999
  ```
964
1000
 
965
1001
  And then in your component taking the page props and putting them in a Context.
1002
+
966
1003
  ```js
967
1004
  function User(props) {
968
1005
  <UserContext.Provider value={props.data}>
969
1006
  {/* my component content */}
970
- </UserContext.Provider>
1007
+ </UserContext.Provider>;
971
1008
  }
972
1009
 
973
1010
  // Somewhere else
974
1011
  function UserDetails() {
975
- const user = useContext(UserContext)
1012
+ const user = useContext(UserContext);
976
1013
  // render stuff
977
1014
  }
978
1015
  ```
@@ -1,6 +1,6 @@
1
1
  import { JSX } from "solid-js";
2
2
  import type { Submission, SubmissionStub, NarrowResponse } from "../types.js";
3
- export type Action<T extends Array<any>, U, V = T> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
3
+ export type Action<T extends Array<any>, U, V = T> = (T extends [FormData | URLSearchParams] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
4
4
  url: string;
5
5
  with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>, ...args: A): Action<B, U, V>;
6
6
  };
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
3
3
  */
4
- import { createResource, sharedConfig, untrack } from "solid-js";
4
+ import { createResource, sharedConfig, untrack, catchError } from "solid-js";
5
5
  import { createStore, reconcile, unwrap } from "solid-js/store";
6
6
  import { isServer } from "solid-js/web";
7
7
  export function createAsync(fn, options) {
8
8
  let resource;
9
9
  let prev = () => !resource || resource.state === "unresolved" ? undefined : resource.latest;
10
- [resource] = createResource(() => subFetch(fn, untrack(prev)), v => v, options);
10
+ [resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, options);
11
11
  const resultAccessor = (() => resource());
12
- Object.defineProperty(resultAccessor, 'latest', {
12
+ Object.defineProperty(resultAccessor, "latest", {
13
13
  get() {
14
14
  return resource.latest;
15
15
  }
@@ -18,13 +18,15 @@ export function createAsync(fn, options) {
18
18
  }
19
19
  export function createAsyncStore(fn, options = {}) {
20
20
  let resource;
21
- let prev = () => !resource || resource.state === "unresolved" ? undefined : unwrap(resource.latest);
22
- [resource] = createResource(() => subFetch(fn, untrack(prev)), v => v, {
21
+ let prev = () => !resource || resource.state === "unresolved"
22
+ ? undefined
23
+ : unwrap(resource.latest);
24
+ [resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, {
23
25
  ...options,
24
26
  storage: (init) => createDeepSignal(init, options.reconcile)
25
27
  });
26
28
  const resultAccessor = (() => resource());
27
- Object.defineProperty(resultAccessor, 'latest', {
29
+ Object.defineProperty(resultAccessor, "latest", {
28
30
  get() {
29
31
  return resource.latest;
30
32
  }
@@ -146,6 +146,15 @@ export function query(fn, name) {
146
146
  function handleResponse(error) {
147
147
  return async (v) => {
148
148
  if (v instanceof Response) {
149
+ const e = getRequestEvent();
150
+ if (e) {
151
+ for (const [key, value] of v.headers) {
152
+ if (key == "set-cookie")
153
+ e.response.headers.append("set-cookie", value);
154
+ else
155
+ e.response.headers.set(key, value);
156
+ }
157
+ }
149
158
  const url = v.headers.get(LocationHeader);
150
159
  if (url !== null) {
151
160
  // client + server relative redirect
@@ -155,11 +164,8 @@ export function query(fn, name) {
155
164
  });
156
165
  else if (!isServer)
157
166
  window.location.href = url;
158
- else if (isServer) {
159
- const e = getRequestEvent();
160
- if (e)
161
- e.response = { status: 302, headers: new Headers({ Location: url }) };
162
- }
167
+ else if (e)
168
+ e.response.status = 302;
163
169
  return;
164
170
  }
165
171
  if (v.customBody)
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export * from "./lifecycle.js";
4
4
  export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
5
5
  export { mergeSearchString as _mergeSearchString } from "./utils.js";
6
6
  export * from "./data/index.js";
7
- export type { Location, LocationChange, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
7
+ export type { Location, LocationChange, SearchParams, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isServer, getRequestEvent, createComponent as createComponent$1, memo, delegateEvents, spread, mergeProps as mergeProps$1, template } from 'solid-js/web';
2
- import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, batch, createComponent, children, mergeProps, Show, createRoot, sharedConfig, getListener, $TRACK, splitProps, createResource } from 'solid-js';
2
+ import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, batch, createComponent, children, mergeProps, Show, createRoot, sharedConfig, getListener, $TRACK, splitProps, createResource, catchError } from 'solid-js';
3
3
  import { createStore, reconcile, unwrap } from 'solid-js/store';
4
4
 
5
5
  function createBeforeLeave() {
@@ -193,6 +193,9 @@ function createMemoObject(fn) {
193
193
  },
194
194
  ownKeys() {
195
195
  return Reflect.ownKeys(fn());
196
+ },
197
+ has(_, property) {
198
+ return property in fn();
196
199
  }
197
200
  });
198
201
  }
@@ -927,7 +930,10 @@ function Routes(props) {
927
930
  }
928
931
  createRoot(dispose => {
929
932
  disposers[i] = dispose;
930
- next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => props.routerState.matches()[i]);
933
+ next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => {
934
+ const routeMatches = props.routerState.matches();
935
+ return routeMatches[i] ?? routeMatches[0];
936
+ });
931
937
  });
932
938
  }
933
939
  }
@@ -1159,6 +1165,12 @@ function query(fn, name) {
1159
1165
  function handleResponse(error) {
1160
1166
  return async v => {
1161
1167
  if (v instanceof Response) {
1168
+ const e = getRequestEvent();
1169
+ if (e) {
1170
+ for (const [key, value] of v.headers) {
1171
+ if (key == "set-cookie") e.response.headers.append("set-cookie", value);else e.response.headers.set(key, value);
1172
+ }
1173
+ }
1162
1174
  const url = v.headers.get(LocationHeader);
1163
1175
  if (url !== null) {
1164
1176
  // client + server relative redirect
@@ -1166,15 +1178,7 @@ function query(fn, name) {
1166
1178
  navigate(url, {
1167
1179
  replace: true
1168
1180
  });
1169
- });else if (!isServer) window.location.href = url;else if (isServer) {
1170
- const e = getRequestEvent();
1171
- if (e) e.response = {
1172
- status: 302,
1173
- headers: new Headers({
1174
- Location: url
1175
- })
1176
- };
1177
- }
1181
+ });else if (!isServer) window.location.href = url;else if (e) e.response.status = 302;
1178
1182
  return;
1179
1183
  }
1180
1184
  if (v.customBody) v = await v.customBody();
@@ -1515,7 +1519,7 @@ function Router(props) {
1515
1519
  saveCurrentDepth();
1516
1520
  },
1517
1521
  init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => {
1518
- if (delta && delta < 0) {
1522
+ if (delta) {
1519
1523
  return !beforeLeave.confirm(delta);
1520
1524
  } else {
1521
1525
  const s = getSource();
@@ -1709,9 +1713,9 @@ function Navigate(props) {
1709
1713
  function createAsync(fn, options) {
1710
1714
  let resource;
1711
1715
  let prev = () => !resource || resource.state === "unresolved" ? undefined : resource.latest;
1712
- [resource] = createResource(() => subFetch(fn, untrack(prev)), v => v, options);
1716
+ [resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, options);
1713
1717
  const resultAccessor = () => resource();
1714
- Object.defineProperty(resultAccessor, 'latest', {
1718
+ Object.defineProperty(resultAccessor, "latest", {
1715
1719
  get() {
1716
1720
  return resource.latest;
1717
1721
  }
@@ -1721,12 +1725,12 @@ function createAsync(fn, options) {
1721
1725
  function createAsyncStore(fn, options = {}) {
1722
1726
  let resource;
1723
1727
  let prev = () => !resource || resource.state === "unresolved" ? undefined : unwrap(resource.latest);
1724
- [resource] = createResource(() => subFetch(fn, untrack(prev)), v => v, {
1728
+ [resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, {
1725
1729
  ...options,
1726
1730
  storage: init => createDeepSignal(init, options.reconcile)
1727
1731
  });
1728
1732
  const resultAccessor = () => resource();
1729
- Object.defineProperty(resultAccessor, 'latest', {
1733
+ Object.defineProperty(resultAccessor, "latest", {
1730
1734
  get() {
1731
1735
  return resource.latest;
1732
1736
  }
@@ -28,7 +28,7 @@ export function Router(props) {
28
28
  saveCurrentDepth();
29
29
  },
30
30
  init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => {
31
- if (delta && delta < 0) {
31
+ if (delta) {
32
32
  return !beforeLeave.confirm(delta);
33
33
  }
34
34
  else {
@@ -1,5 +1,5 @@
1
1
  import type { Component, JSX } from "solid-js";
2
- import type { MatchFilters, RouteDefinition, RouterIntegration, RouteSectionProps, RoutePreloadFunc } from "../types.js";
2
+ import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionProps } from "../types.js";
3
3
  export type BaseRouterProps = {
4
4
  base?: string;
5
5
  /**
@@ -1,6 +1,6 @@
1
1
  /*@refresh skip*/
2
- import { getRequestEvent, isServer } from "solid-js/web";
3
2
  import { children, createMemo, createRoot, getOwner, mergeProps, on, Show, untrack } from "solid-js";
3
+ import { getRequestEvent, isServer } from "solid-js/web";
4
4
  import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
5
5
  export const createRouterComponent = (router) => (props) => {
6
6
  const { base } = props;
@@ -70,7 +70,10 @@ function Routes(props) {
70
70
  }
71
71
  createRoot(dispose => {
72
72
  disposers[i] = dispose;
73
- next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => props.routerState.matches()[i]);
73
+ next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => {
74
+ const routeMatches = props.routerState.matches();
75
+ return routeMatches[i] ?? routeMatches[0];
76
+ });
74
77
  });
75
78
  }
76
79
  }
package/dist/types.d.ts CHANGED
@@ -21,8 +21,8 @@ declare module "solid-js/web" {
21
21
  serverOnly?: boolean;
22
22
  }
23
23
  }
24
- export type Params = Record<string, string>;
25
- export type SearchParams = Record<string, string | string[]>;
24
+ export type Params = Record<string, string | undefined>;
25
+ export type SearchParams = Record<string, string | string[] | undefined>;
26
26
  export type SetParams = Record<string, string | number | boolean | null | undefined>;
27
27
  export type SetSearchParams = Record<string, string | string[] | number | number[] | boolean | boolean[] | null | undefined>;
28
28
  export interface Path {
package/dist/utils.js CHANGED
@@ -129,6 +129,9 @@ export function createMemoObject(fn) {
129
129
  },
130
130
  ownKeys() {
131
131
  return Reflect.ownKeys(fn());
132
+ },
133
+ has(_, property) {
134
+ return property in fn();
132
135
  }
133
136
  });
134
137
  }
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.15.3",
9
+ "version": "0.15.4",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -55,6 +55,7 @@
55
55
  "test": "vitest run && npm run test:types",
56
56
  "test:watch": "vitest",
57
57
  "test:types": "tsc --project tsconfig.test.json",
58
- "pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\""
58
+ "pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
59
+ "release": "pnpm build && changeset publish"
59
60
  }
60
61
  }