@solidjs/router 0.12.4 → 0.13.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
@@ -59,7 +59,7 @@ This sets up a Router that will match on the url to display the desired page
59
59
 
60
60
  Solid Router allows you to configure your routes using JSX:
61
61
 
62
- 1. Add each route to a `<Router>` using the `Route` component, specifying a path and an element or component to render when the user navigates to that path.
62
+ 1. Add each route to a `<Router>` using the `Route` component, specifying a path and a component to render when the user navigates to that path.
63
63
 
64
64
  ```jsx
65
65
  import { render } from "solid-js/web";
@@ -494,6 +494,14 @@ const user = createAsync((currentValue) => getUser(params.id))
494
494
 
495
495
  Using `cache` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
496
496
 
497
+ ### `createAsyncStore`
498
+
499
+ Similar to `createAsync` except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
500
+
501
+ ```jsx
502
+ const todos = createAsyncStore(() => getTodos());
503
+ ```
504
+
497
505
  ### `action`
498
506
 
499
507
  Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
@@ -26,21 +26,28 @@ export function useSubmission(fn, filter) {
26
26
  });
27
27
  }
28
28
  export function useAction(action) {
29
- const router = useRouter();
30
- return (...args) => action.apply(router, args);
29
+ const r = useRouter();
30
+ return (...args) => action.apply({ r }, args);
31
31
  }
32
32
  export function action(fn, name) {
33
33
  function mutate(...variables) {
34
- const router = this;
34
+ const router = this.r;
35
+ const form = this.f;
35
36
  const p = (router.singleFlight && fn.withOptions
36
37
  ? fn.withOptions({ headers: { "X-Single-Flight": "true" } })
37
38
  : fn)(...variables);
38
39
  const [result, setResult] = createSignal();
39
40
  let submission;
40
- async function handler(res) {
41
- const data = await handleResponse(res, router.navigatorFactory());
42
- data ? setResult({ data }) : submission.clear();
43
- return data;
41
+ function handler(error) {
42
+ return async (res) => {
43
+ const result = await handleResponse(res, error, router.navigatorFactory());
44
+ if (!result)
45
+ return submission.clear();
46
+ setResult(result);
47
+ if (result.error && !form)
48
+ throw result.error;
49
+ return result.data;
50
+ };
44
51
  }
45
52
  router.submissions[1](s => [
46
53
  ...s,
@@ -50,6 +57,9 @@ export function action(fn, name) {
50
57
  get result() {
51
58
  return result()?.data;
52
59
  },
60
+ get error() {
61
+ return result()?.error;
62
+ },
53
63
  get pending() {
54
64
  return !result();
55
65
  },
@@ -59,11 +69,11 @@ export function action(fn, name) {
59
69
  retry() {
60
70
  setResult(undefined);
61
71
  const p = fn(...variables);
62
- return p.then(handler, handler);
72
+ return p.then(handler(), handler(true));
63
73
  }
64
74
  })
65
75
  ]);
66
- return p.then(handler, handler);
76
+ return p.then(handler(), handler(true));
67
77
  }
68
78
  const url = fn.url ||
69
79
  (name && `https://action/${name}`) ||
@@ -92,7 +102,7 @@ function toAction(fn, url) {
92
102
  return fn;
93
103
  }
94
104
  const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
95
- async function handleResponse(response, navigate) {
105
+ async function handleResponse(response, error, navigate) {
96
106
  let data;
97
107
  let keys;
98
108
  let invalidateKeys;
@@ -123,11 +133,13 @@ async function handleResponse(response, navigate) {
123
133
  }
124
134
  }
125
135
  }
136
+ else if (error)
137
+ return { error: response };
126
138
  else
127
139
  data = response;
128
140
  // invalidate
129
141
  cacheKeyOp(invalidateKeys, entry => (entry[0] = 0));
130
142
  // trigger revalidation
131
143
  await revalidate(keys, false);
132
- return data;
144
+ return data != null ? { data } : undefined;
133
145
  }
@@ -73,9 +73,10 @@ export function cache(fn, name) {
73
73
  onCleanup(() => cached[3].count--);
74
74
  }
75
75
  if (cached &&
76
+ cached[0] &&
76
77
  (isServer ||
77
78
  intent === "native" ||
78
- (cached[0] && cached[3].count) ||
79
+ cached[3].count ||
79
80
  Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
80
81
  if (tracking) {
81
82
  cached[3].count++;
@@ -103,7 +103,7 @@ export function setupNativeEvents(preload = true, explicitLinks = false, actionB
103
103
  const data = new FormData(evt.target);
104
104
  if (evt.submitter && evt.submitter.name)
105
105
  data.append(evt.submitter.name, evt.submitter.value);
106
- handler.call(router, data);
106
+ handler.call({ r: router, f: evt.target }, data);
107
107
  }
108
108
  }
109
109
  // ensure delegated event run first
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, createComponent, children, mergeProps, createRoot, Show, getListener, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js';
2
+ import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, mergeProps, Show, createRoot, getListener, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js';
3
3
  import { createStore, reconcile, unwrap } from 'solid-js/store';
4
4
 
5
5
  function createBeforeLeave() {
@@ -230,7 +230,7 @@ function expandOptionals(pattern) {
230
230
  const MAX_REDIRECTS = 100;
231
231
  const RouterContextObj = createContext();
232
232
  const RouteContextObj = createContext();
233
- const useRouter = () => invariant(useContext(RouterContextObj), "Make sure your app is wrapped in a <Router />");
233
+ const useRouter = () => invariant(useContext(RouterContextObj), "<A> and 'use' router primitives can be only used inside a Route.");
234
234
  const useRoute = () => useContext(RouteContextObj) || useRouter().base;
235
235
  const useResolvedPath = path => {
236
236
  const route = useRoute();
@@ -642,35 +642,70 @@ const createRouterComponent = router => props => {
642
642
  base
643
643
  } = props;
644
644
  const routeDefs = children(() => props.children);
645
- const branches = createMemo(() => createBranches(props.root ? {
646
- component: props.root,
647
- load: props.rootLoad,
648
- children: routeDefs()
649
- } : routeDefs(), props.base || ""));
645
+ const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
650
646
  let context;
651
647
  const routerState = createRouterContext(router, () => context, branches, {
652
648
  base,
653
649
  singleFlight: props.singleFlight
654
650
  });
651
+ const location = routerState.location;
655
652
  router.create && router.create(routerState);
656
653
  return createComponent$1(RouterContextObj.Provider, {
657
654
  value: routerState,
658
655
  get children() {
659
- return [memo(() => (context = getOwner()) && null), createComponent$1(Routes, {
660
- routerState: routerState,
661
- get branches() {
662
- return branches();
656
+ return createComponent$1(Root, {
657
+ location: location,
658
+ get root() {
659
+ return props.root;
660
+ },
661
+ get load() {
662
+ return props.rootLoad;
663
+ },
664
+ get children() {
665
+ return [memo(() => (context = getOwner()) && null), createComponent$1(Routes, {
666
+ routerState: routerState,
667
+ get branches() {
668
+ return branches();
669
+ }
670
+ })];
663
671
  }
664
- })];
672
+ });
665
673
  }
666
674
  });
667
675
  };
676
+ function Root(props) {
677
+ const location = props.location;
678
+ const data = createMemo(() => props.load && untrack(() => props.load({
679
+ params: {},
680
+ location,
681
+ intent: "preload"
682
+ })));
683
+ return createComponent$1(Show, {
684
+ get when() {
685
+ return props.root;
686
+ },
687
+ keyed: true,
688
+ get fallback() {
689
+ return props.children;
690
+ },
691
+ children: Root => createComponent$1(Root, {
692
+ params: {},
693
+ location: location,
694
+ get data() {
695
+ return data();
696
+ },
697
+ get children() {
698
+ return props.children;
699
+ }
700
+ })
701
+ });
702
+ }
668
703
  function Routes(props) {
669
704
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
670
705
  if (isServer) {
671
706
  const e = getRequestEvent();
672
707
  if (e && e.router && e.router.dataOnly) {
673
- dataOnly(e, props.branches);
708
+ dataOnly(e, props.routerState, props.branches);
674
709
  return;
675
710
  }
676
711
  e && ((e.router || (e.router = {})).matches || (e.router.matches = matches().map(({
@@ -721,18 +756,7 @@ function Routes(props) {
721
756
  root = next[0];
722
757
  return next;
723
758
  }));
724
- return createComponent$1(Show, {
725
- get when() {
726
- return routeStates() && root;
727
- },
728
- keyed: true,
729
- children: route => createComponent$1(RouteContextObj.Provider, {
730
- value: route,
731
- get children() {
732
- return route.outlet();
733
- }
734
- })
735
- });
759
+ return createOutlet(() => routeStates() && root)();
736
760
  }
737
761
  const createOutlet = child => {
738
762
  return () => createComponent$1(Show, {
@@ -758,7 +782,7 @@ const Route = props => {
758
782
  };
759
783
 
760
784
  // for data only mode with single flight mutations
761
- function dataOnly(event, branches) {
785
+ function dataOnly(event, routerState, branches) {
762
786
  const url = new URL(event.request.url);
763
787
  const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
764
788
  const matches = getRouteMatches(branches, url.pathname);
@@ -770,14 +794,7 @@ function dataOnly(event, branches) {
770
794
  } = matches[match];
771
795
  route.load && route.load({
772
796
  params,
773
- location: {
774
- pathname: url.pathname,
775
- search: url.search,
776
- hash: url.hash,
777
- query: extractSearchParams(url),
778
- state: null,
779
- key: ""
780
- },
797
+ location: routerState.location,
781
798
  intent: "preload"
782
799
  });
783
800
  }
@@ -913,7 +930,7 @@ function cache(fn, name) {
913
930
  tracking = true;
914
931
  onCleanup(() => cached[3].count--);
915
932
  }
916
- if (cached && (isServer || intent === "native" || cached[0] && cached[3].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
933
+ if (cached && cached[0] && (isServer || intent === "native" || cached[3].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
917
934
  if (tracking) {
918
935
  cached[3].count++;
919
936
  cached[3][0](); // track
@@ -1040,12 +1057,15 @@ function useSubmission(fn, filter) {
1040
1057
  });
1041
1058
  }
1042
1059
  function useAction(action) {
1043
- const router = useRouter();
1044
- return (...args) => action.apply(router, args);
1060
+ const r = useRouter();
1061
+ return (...args) => action.apply({
1062
+ r
1063
+ }, args);
1045
1064
  }
1046
1065
  function action(fn, name) {
1047
1066
  function mutate(...variables) {
1048
- const router = this;
1067
+ const router = this.r;
1068
+ const form = this.f;
1049
1069
  const p = (router.singleFlight && fn.withOptions ? fn.withOptions({
1050
1070
  headers: {
1051
1071
  "X-Single-Flight": "true"
@@ -1053,12 +1073,14 @@ function action(fn, name) {
1053
1073
  }) : fn)(...variables);
1054
1074
  const [result, setResult] = createSignal();
1055
1075
  let submission;
1056
- async function handler(res) {
1057
- const data = await handleResponse(res, router.navigatorFactory());
1058
- data ? setResult({
1059
- data
1060
- }) : submission.clear();
1061
- return data;
1076
+ function handler(error) {
1077
+ return async res => {
1078
+ const result = await handleResponse(res, error, router.navigatorFactory());
1079
+ if (!result) return submission.clear();
1080
+ setResult(result);
1081
+ if (result.error && !form) throw result.error;
1082
+ return result.data;
1083
+ };
1062
1084
  }
1063
1085
  router.submissions[1](s => [...s, submission = {
1064
1086
  input: variables,
@@ -1066,6 +1088,9 @@ function action(fn, name) {
1066
1088
  get result() {
1067
1089
  return result()?.data;
1068
1090
  },
1091
+ get error() {
1092
+ return result()?.error;
1093
+ },
1069
1094
  get pending() {
1070
1095
  return !result();
1071
1096
  },
@@ -1075,10 +1100,10 @@ function action(fn, name) {
1075
1100
  retry() {
1076
1101
  setResult(undefined);
1077
1102
  const p = fn(...variables);
1078
- return p.then(handler, handler);
1103
+ return p.then(handler(), handler(true));
1079
1104
  }
1080
1105
  }]);
1081
- return p.then(handler, handler);
1106
+ return p.then(handler(), handler(true));
1082
1107
  }
1083
1108
  const url = fn.url || name && `https://action/${name}` || (!isServer ? `https://action/${hashString(fn.toString())}` : "");
1084
1109
  return toAction(mutate, url);
@@ -1104,7 +1129,7 @@ function toAction(fn, url) {
1104
1129
  return fn;
1105
1130
  }
1106
1131
  const hashString = s => s.split("").reduce((a, b) => (a << 5) - a + b.charCodeAt(0) | 0, 0);
1107
- async function handleResponse(response, navigate) {
1132
+ async function handleResponse(response, error, navigate) {
1108
1133
  let data;
1109
1134
  let keys;
1110
1135
  let invalidateKeys;
@@ -1131,12 +1156,16 @@ async function handleResponse(response, navigate) {
1131
1156
  navigate(locationUrl);
1132
1157
  }
1133
1158
  }
1134
- } else data = response;
1159
+ } else if (error) return {
1160
+ error: response
1161
+ };else data = response;
1135
1162
  // invalidate
1136
1163
  cacheKeyOp(invalidateKeys, entry => entry[0] = 0);
1137
1164
  // trigger revalidation
1138
1165
  await revalidate(keys, false);
1139
- return data;
1166
+ return data != null ? {
1167
+ data
1168
+ } : undefined;
1140
1169
  }
1141
1170
 
1142
1171
  function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server") {
@@ -1215,7 +1244,10 @@ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "
1215
1244
  evt.preventDefault();
1216
1245
  const data = new FormData(evt.target);
1217
1246
  if (evt.submitter && evt.submitter.name) data.append(evt.submitter.name, evt.submitter.value);
1218
- handler.call(router, data);
1247
+ handler.call({
1248
+ r: router,
1249
+ f: evt.target
1250
+ }, data);
1219
1251
  }
1220
1252
  }
1221
1253
 
@@ -1,26 +1,41 @@
1
1
  /*@refresh skip*/
2
2
  import { getRequestEvent, isServer } from "solid-js/web";
3
- import { children, createMemo, createRoot, getOwner, mergeProps, on, Show } from "solid-js";
3
+ import { children, createMemo, createRoot, getOwner, mergeProps, on, Show, untrack } from "solid-js";
4
4
  import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj } from "../routing.js";
5
- import { createMemoObject, extractSearchParams } from "../utils.js";
5
+ import { createMemoObject } from "../utils.js";
6
6
  export const createRouterComponent = (router) => (props) => {
7
7
  const { base } = props;
8
8
  const routeDefs = children(() => props.children);
9
- const branches = createMemo(() => createBranches(props.root ? { component: props.root, load: props.rootLoad, children: routeDefs() } : routeDefs(), props.base || ""));
9
+ const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
10
10
  let context;
11
- const routerState = createRouterContext(router, () => context, branches, { base, singleFlight: props.singleFlight });
11
+ const routerState = createRouterContext(router, () => context, branches, {
12
+ base,
13
+ singleFlight: props.singleFlight
14
+ });
15
+ const location = routerState.location;
12
16
  router.create && router.create(routerState);
13
17
  return (<RouterContextObj.Provider value={routerState}>
14
- {(context = getOwner()) && null}
15
- <Routes routerState={routerState} branches={branches()}/>
18
+ <Root location={location} root={props.root} load={props.rootLoad}>
19
+ {(context = getOwner()) && null}
20
+ <Routes routerState={routerState} branches={branches()}/>
21
+ </Root>
16
22
  </RouterContextObj.Provider>);
17
23
  };
24
+ function Root(props) {
25
+ const location = props.location;
26
+ const data = createMemo(() => props.load && untrack(() => props.load({ params: {}, location, intent: "preload" })));
27
+ return (<Show when={props.root} keyed fallback={props.children}>
28
+ {Root => (<Root params={{}} location={location} data={data()}>
29
+ {props.children}
30
+ </Root>)}
31
+ </Show>);
32
+ }
18
33
  function Routes(props) {
19
34
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
20
35
  if (isServer) {
21
36
  const e = getRequestEvent();
22
37
  if (e && e.router && e.router.dataOnly) {
23
- dataOnly(e, props.branches);
38
+ dataOnly(e, props.routerState, props.branches);
24
39
  return;
25
40
  }
26
41
  e &&
@@ -70,9 +85,7 @@ function Routes(props) {
70
85
  root = next[0];
71
86
  return next;
72
87
  }));
73
- return (<Show when={routeStates() && root} keyed>
74
- {route => <RouteContextObj.Provider value={route}>{route.outlet()}</RouteContextObj.Provider>}
75
- </Show>);
88
+ return createOutlet(() => routeStates() && root)();
76
89
  }
77
90
  const createOutlet = (child) => {
78
91
  return () => (<Show when={child()} keyed>
@@ -88,7 +101,7 @@ export const Route = (props) => {
88
101
  });
89
102
  };
90
103
  // for data only mode with single flight mutations
91
- function dataOnly(event, branches) {
104
+ function dataOnly(event, routerState, branches) {
92
105
  const url = new URL(event.request.url);
93
106
  const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
94
107
  const matches = getRouteMatches(branches, url.pathname);
@@ -99,14 +112,7 @@ function dataOnly(event, branches) {
99
112
  route.load &&
100
113
  route.load({
101
114
  params,
102
- location: {
103
- pathname: url.pathname,
104
- search: url.search,
105
- hash: url.hash,
106
- query: extractSearchParams(url),
107
- state: null,
108
- key: ""
109
- },
115
+ location: routerState.location,
110
116
  intent: "preload"
111
117
  });
112
118
  }
package/dist/routing.js CHANGED
@@ -6,7 +6,7 @@ import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath
6
6
  const MAX_REDIRECTS = 100;
7
7
  export const RouterContextObj = createContext();
8
8
  export const RouteContextObj = createContext();
9
- export const useRouter = () => invariant(useContext(RouterContextObj), "Make sure your app is wrapped in a <Router />");
9
+ export const useRouter = () => invariant(useContext(RouterContextObj), "<A> and 'use' router primitives can be only used inside a Route.");
10
10
  let TempRoute;
11
11
  export const useRoute = () => TempRoute || useContext(RouteContextObj) || useRouter().base;
12
12
  export const useResolvedPath = (path) => {
@@ -352,7 +352,9 @@ export function createRouterContext(integration, getContext, getBranches, option
352
352
  }
353
353
  function initFromFlash() {
354
354
  const e = getRequestEvent();
355
- return e && e.router && e.router.submission ? [e.router.submission] : [];
355
+ return (e && e.router && e.router.submission
356
+ ? [e.router.submission]
357
+ : []);
356
358
  }
357
359
  }
358
360
  export function createRouteContext(router, parent, outlet, match, params) {
package/dist/types.d.ts CHANGED
@@ -9,7 +9,11 @@ declare module "solid-js/web" {
9
9
  router?: {
10
10
  matches?: OutputMatch[];
11
11
  cache?: Map<string, CacheEntry>;
12
- submission?: Submission<any, any>;
12
+ submission?: {
13
+ input: any;
14
+ result: any;
15
+ url: string;
16
+ };
13
17
  dataOnly?: boolean | string[];
14
18
  data?: Record<string, any>;
15
19
  previousUrl?: string;
@@ -153,6 +157,7 @@ export interface BeforeLeaveLifecycle {
153
157
  export type Submission<T, U> = {
154
158
  readonly input: T;
155
159
  readonly result?: U;
160
+ readonly error: any;
156
161
  readonly pending: boolean;
157
162
  readonly url: string;
158
163
  clear: () => void;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.12.4",
9
+ "version": "0.13.0",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",