Skip to content

Commit 59dcbcc

Browse files
committed
Merge branch 'main' into mattrax-main
2 parents dc94116 + 098a794 commit 59dcbcc

15 files changed

Lines changed: 288 additions & 170 deletions

.changeset/breezy-trainers-marry.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/green-mice-build.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
# @solidjs/router
22

3+
## 0.14.1
4+
5+
### Patch Changes
6+
7+
- 6144da8: fix Response type narrowing in submission APIs
8+
9+
## 0.14.0
10+
11+
### Minor Changes
12+
13+
- e4a13f6: Response helpers return responses, cache/action filter them out
14+
- bd9f19a: default form actions to url encoded
15+
- 5d9263b: rename load to preload
16+
17+
### Patch Changes
18+
19+
- a7e4062: fix #457 extra leading slashes on path
20+
- 4b4536e: add usePreloadRoute export
21+
- 8cc0530: hack the types to work a bit better with Response Unions
22+
23+
## 0.13.6
24+
25+
### Patch Changes
26+
27+
- 7344f69: Handle absolute redirects within `cache` on server
28+
- 8263115: Forward absolute redirects inside `cache` from server to client
29+
- 8fbf74a: Treat `window.location.hash` as URI encoded
30+
- e9fd55d: fix #449 No JS submissions not working
31+
- f311f4a: fix #452 useSubmission types/references
32+
- 2f05f37: Make isRouting more reliable + other fixes
33+
- 618ef17: performance improvement leveraging redirects in loadfn
34+
- d81473a: usePreloadRoute method pre-release
35+
336
## 0.13.5
437

538
### Patch Changes

README.md

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A router lets you change your view based on the URL in the browser. This allows
1010

1111
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.
1212

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 load function that loads parallel to the routes ([render-as-you-fetch](https://epicreact.dev/render-as-you-fetch/)).
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/)).
1414

1515
- [Getting Started](#getting-started)
1616
- [Set Up the Router](#set-up-the-router)
@@ -380,25 +380,25 @@ You can nest indefinitely - just remember that only leaf nodes will become their
380380
</Route>
381381
```
382382

383-
## Load Functions
383+
## Preload Functions
384384

385-
Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With load functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible. The load function is called when the Route is loaded or eagerly when links are hovered.
385+
Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With preload functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible. The preload function is called when the Route is loaded or eagerly when links are hovered.
386386

387-
As its only argument, the load function is passed an object that you can use to access route information:
387+
As its only argument, the preload function is passed an object that you can use to access route information:
388388

389389
```js
390390
import { lazy } from "solid-js";
391391
import { Route } from "@solidjs/router";
392392

393393
const User = lazy(() => import("./pages/users/[id].js"));
394394

395-
// load function
396-
function loadUser({params, location}) {
397-
// do loading
395+
// preload function
396+
function preloadUser({params, location}) {
397+
// do preloading
398398
}
399399

400400
// Pass it in the route definition
401-
<Route path="/users/:id" component={User} load={loadUser} />;
401+
<Route path="/users/:id" component={User} preload={preloadUser} />;
402402
```
403403

404404
| key | type | description |
@@ -408,24 +408,24 @@ function loadUser({params, location}) {
408408
| 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> |
409409

410410

411-
A common pattern is to export the load 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.
411+
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.
412412

413413
```js
414414
import { lazy } from "solid-js";
415415
import { Route } from "@solidjs/router";
416-
import loadUser from "./pages/users/[id].data.js";
416+
import preloadUser from "./pages/users/[id].data.js";
417417
const User = lazy(() => import("/pages/users/[id].js"));
418418

419419
// In the Route definition
420-
<Route path="/users/:id" component={User} load={loadUser} />;
420+
<Route path="/users/:id" component={User} preload={preloadUser} />;
421421
```
422422

423-
The return value of the `load` function is passed to the page component when called at anytime other than `"preload"`, so you can initialize things in there, or alternatively use our new Data APIs:
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:
424424

425425

426426
## Data APIs
427427

428-
Keep in mind these are completely optional. To use but showcase the power of our load mechanism.
428+
Keep in mind these are completely optional. To use but showcase the power of our preload mechanism.
429429

430430
### `cache`
431431

@@ -441,11 +441,11 @@ It is expected that the arguments to the cache function are serializable.
441441
This cache accomplishes the following:
442442

443443
1. It does just deduping on the server for the lifetime of the request.
444-
2. It does preload cache in the browser which lasts 10 seconds. When a route is preloaded on hover or when load is called when entering a route it will make sure to dedupe calls.
444+
2. It does preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
445445
3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
446446
4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses it. Revalidation or new fetch updates the cache.
447447

448-
Using it with load function might look like:
448+
Using it with preload function might look like:
449449

450450
```js
451451
import { lazy } from "solid-js";
@@ -454,13 +454,13 @@ import { getUser } from ... // the cache function
454454

455455
const User = lazy(() => import("./pages/users/[id].js"));
456456

457-
// load function
458-
function loadUser({params, location}) {
457+
// preload function
458+
function preloadUser({params, location}) {
459459
void getUser(params.id)
460460
}
461461

462462
// Pass it in the route definition
463-
<Route path="/users/:id" component={User} load={loadUser} />;
463+
<Route path="/users/:id" component={User} preload={preloadUser} />;
464464
```
465465

466466
Inside your page component you:
@@ -770,7 +770,7 @@ The Component for defining Routes:
770770
| component | `Component` | Component that will be rendered for the matched segment |
771771
| matchFilters | `MatchFilters` | Additional constraints for matching against the route |
772772
| children | `JSX.Element` | Nested `<Route>` definitions |
773-
| load | `RouteLoadFunc` | Function called during preload or when the route is navigated to. |
773+
| preload | `RoutePreloadFunc` | Function called during preload or when the route is navigated to. |
774774

775775
## Router Primitives
776776

@@ -873,6 +873,16 @@ const matches = useCurrentMatches();
873873
const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
874874
```
875875

876+
### usePreloadRoute
877+
878+
`usePreloadRoute` returns a function that can be used to preload a route manual. This is what happens automatically with link hovering and similar focus based behavior, but it is available here as an API.
879+
880+
```js
881+
const preload = usePreloadRoute();
882+
883+
preload(`/users/settings`, { preloadData: true });
884+
```
885+
876886
### useBeforeLeave
877887

878888
`useBeforeLeave` takes a function that will be called prior to leaving a route. The function will be called with:
@@ -919,7 +929,7 @@ Related without Outlet component it has to be passed in manually. At which point
919929

920930
### `data` functions & `useRouteData`
921931

922-
These have been replaced by a load mechanism. This allows link hover preloads (as the load function can be run as much as wanted without worry about reactivity). It support deduping/cache 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.
932+
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/cache 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.
923933

924934
That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
925935

@@ -929,15 +939,15 @@ import { Route } from "@solidjs/router";
929939

930940
const User = lazy(() => import("./pages/users/[id].js"));
931941

932-
// load function
933-
function loadUser({params, location}) {
942+
// preload function
943+
function preloadUser({params, location}) {
934944
const [user] = createResource(() => params.id, fetchUser);
935945
return user;
936946
}
937947

938948
// Pass it in the route definition
939949
<Router preload={false}>
940-
<Route path="/users/:id" component={User} load={loadUser} />
950+
<Route path="/users/:id" component={User} preload={preloadUser} />
941951
</Router>
942952
```
943953

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"Ryan Turnquist"
77
],
88
"license": "MIT",
9-
"version": "0.13.5",
9+
"version": "0.14.1",
1010
"homepage": "https://github.com/solidjs/solid-router#readme",
1111
"repository": {
1212
"type": "git",

src/data/action.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { $TRACK, createMemo, createSignal, JSX, onCleanup, getOwner } from "solid-js";
22
import { isServer } from "solid-js/web";
33
import { useRouter } from "../routing.js";
4-
import { RouterContext, Submission, Navigator } from "../types.js";
4+
import type { RouterContext, Submission, SubmissionStub, Navigator, NarrowResponse } from "../types.js";
55
import { mockBase } from "../utils.js";
66
import { cacheKeyOp, hashKey, revalidate, cache } from "./cache.js";
77

88
export type Action<T extends Array<any>, U> = (T extends [FormData] | []
99
? JSX.SerializableAttributeValue
1010
: unknown) &
11-
((...vars: T) => Promise<U>) & {
11+
((...vars: T) => Promise<NarrowResponse<U>>) & {
1212
url: string;
1313
with<A extends any[], B extends any[]>(
14-
this: (this: any, ...args: [...A, ...B]) => Promise<U>,
14+
this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>,
1515
...args: A
1616
): Action<B, U>;
1717
};
@@ -21,7 +21,7 @@ export const actions = /* #__PURE__ */ new Map<string, Action<any, any>>();
2121
export function useSubmissions<T extends Array<any>, U>(
2222
fn: Action<T, U>,
2323
filter?: (arg: T) => boolean
24-
): Submission<T, U>[] & { pending: boolean } {
24+
): Submission<T, NarrowResponse<U>>[] & { pending: boolean } {
2525
const router = useRouter();
2626
const subs = createMemo(() =>
2727
router.submissions[0]().filter(s => s.url === fn.toString() && (!filter || filter(s.input)))
@@ -38,16 +38,17 @@ export function useSubmissions<T extends Array<any>, U>(
3838
export function useSubmission<T extends Array<any>, U>(
3939
fn: Action<T, U>,
4040
filter?: (arg: T) => boolean
41-
): Submission<T, U> {
41+
): Submission<T, NarrowResponse<U>> | SubmissionStub {
4242
const submissions = useSubmissions(fn, filter);
4343
return new Proxy(
4444
{},
4545
{
4646
get(_, property) {
47+
if (submissions.length === 0 && property === "clear" || property === "retry") return (() => {});
4748
return submissions[submissions.length - 1]?.[property as keyof Submission<T, U>];
4849
}
4950
}
50-
) as Submission<T, U>;
51+
) as Submission<T, NarrowResponse<U>>;
5152
}
5253

5354
export function useAction<T extends Array<any>, U>(action: Action<T, U>) {

src/data/cache.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
startTransition
99
} from "solid-js";
1010
import { getRequestEvent, isServer } from "solid-js/web";
11-
import { useNavigate, getIntent, getInLoadFn } from "../routing.js";
12-
import { CacheEntry } from "../types.js";
11+
import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
12+
import type { CacheEntry, NarrowResponse } from "../types.js";
1313

1414
const LocationHeader = "Location";
1515
const PRELOAD_TIMEOUT = 5000;
@@ -56,8 +56,12 @@ export type CachedFunction<T extends (...args: any) => any> = T extends (
5656
...args: infer A
5757
) => infer R
5858
? ([] extends { [K in keyof A]-?: A[K] } // A tuple full of optional values is equivalent to an empty tuple
59-
? (...args: never[]) => R
60-
: T) & {
59+
? (
60+
...args: never[]
61+
) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R>
62+
: (
63+
...args: A
64+
) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R>) & {
6165
keyFor: (...args: A) => string;
6266
key: string;
6367
}
@@ -69,7 +73,7 @@ export function cache<T extends (...args: any) => any>(fn: T, name: string): Cac
6973
const cachedFn = ((...args: Parameters<T>) => {
7074
const cache = getCache();
7175
const intent = getIntent();
72-
const inLoadFn = getInLoadFn();
76+
const inPreloadFn = getInPreloadFn();
7377
const owner = getOwner();
7478
const navigate = owner ? useNavigate() : undefined;
7579
const now = Date.now();
@@ -111,13 +115,14 @@ export function cache<T extends (...args: any) => any>(fn: T, name: string): Cac
111115
cached[0] = now;
112116
}
113117
let res = cached[1];
114-
if (!inLoadFn) {
118+
if (intent !== "preload") {
115119
res =
116120
"then" in cached[1]
117121
? cached[1].then(handleResponse(false), handleResponse(true))
118122
: handleResponse(false)(cached[1]);
119123
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
120-
} else "then" in res && res.catch(() => {});
124+
}
125+
inPreloadFn && "then" in res && res.catch(() => {});
121126
return res;
122127
}
123128
let res =
@@ -145,12 +150,13 @@ export function cache<T extends (...args: any) => any>(fn: T, name: string): Cac
145150
const e = getRequestEvent();
146151
if (e && e.router!.dataOnly) return (e.router!.data![key] = res);
147152
}
148-
if (!inLoadFn) {
153+
if (intent !== "preload") {
149154
res =
150155
"then" in res
151156
? res.then(handleResponse(false), handleResponse(true))
152157
: handleResponse(false)(res);
153-
} else "then" in res && res.catch(() => {});
158+
}
159+
inPreloadFn && "then" in res && res.catch(() => {});
154160
// serialize on server
155161
if (
156162
isServer &&
@@ -169,19 +175,18 @@ export function cache<T extends (...args: any) => any>(fn: T, name: string): Cac
169175
const url = v.headers.get(LocationHeader);
170176

171177
if (url !== null) {
172-
let returnEarly = true;
173-
174178
// client + server relative redirect
175179
if (navigate && url.startsWith("/"))
176180
startTransition(() => {
177181
navigate(url, { replace: true });
178182
});
179-
// client-only absolute redirect (possibly cross-origin)
180-
else if (!isServer && url) window.location.href = url;
181-
// server-only absolute redirects are handled on the client
182-
else if (isServer) returnEarly = false;
183+
else if (!isServer) window.location.href = url;
184+
else if (isServer) {
185+
const e = getRequestEvent();
186+
if (e) e.response = { status: 302, headers: new Headers({ Location: url }) };
187+
}
183188

184-
if (returnEarly) return;
189+
return;
185190
}
186191

187192
if ((v as any).customBody) v = await (v as any).customBody();
@@ -190,7 +195,7 @@ export function cache<T extends (...args: any) => any>(fn: T, name: string): Cac
190195
return v;
191196
};
192197
}
193-
}) as CachedFunction<T>;
198+
}) as unknown as CachedFunction<T>;
194199
cachedFn.keyFor = (...args: Parameters<T>) => name + hashKey(args);
195200
cachedFn.key = name;
196201
return cachedFn;

0 commit comments

Comments
 (0)