Skip to content

Commit 3b0bd37

Browse files
committed
refactor: Begin to merge location & route contexts
1 parent 7097769 commit 3b0bd37

File tree

3 files changed

+57
-69
lines changed

3 files changed

+57
-69
lines changed

Diff for: src/router.d.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,12 @@ export function Router(props: {
1414
interface LocationHook {
1515
url: string;
1616
path: string;
17-
query: Record<string, string>;
17+
pathParams: Record<string, string>;
18+
searchParams: Record<string, string>;
1819
route: (url: string, replace?: boolean) => void;
1920
}
2021
export const useLocation: () => LocationHook;
2122

22-
interface RouteHook {
23-
path: string;
24-
query: Record<string, string>;
25-
params: Record<string, string>;
26-
}
27-
export const useRoute: () => RouteHook;
28-
2923
interface RoutableProps {
3024
path?: string;
3125
default?: boolean;

Diff for: src/router.js

+15-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { h, createContext, cloneElement, toChildArray } from 'preact';
1+
import { h, Fragment, createContext, cloneElement, toChildArray } from 'preact';
22
import { useContext, useMemo, useReducer, useLayoutEffect, useRef } from 'preact/hooks';
33

44
/**
@@ -47,10 +47,10 @@ export const exec = (url, route, matches) => {
4747
url = url.split('/').filter(Boolean);
4848
route = (route || '').split('/').filter(Boolean);
4949
for (let i = 0, val, rest; i < Math.max(url.length, route.length); i++) {
50-
let [, m, param, flag] = (route[i] || '').match(/^(:?)(.*?)([+*?]?)$/);
50+
let [, m, pathParam, flag] = (route[i] || '').match(/^(:?)(.*?)([+*?]?)$/);
5151
val = url[i];
5252
// segment match:
53-
if (!m && param == val) continue;
53+
if (!m && pathParam == val) continue;
5454
// /foo/* match
5555
if (!m && val && flag == '*') {
5656
matches.rest = '/' + url.slice(i).map(decodeURIComponent).join('/');
@@ -63,8 +63,8 @@ export const exec = (url, route, matches) => {
6363
if (rest) val = url.slice(i).map(decodeURIComponent).join('/');
6464
// normal/optional field:
6565
else if (val) val = decodeURIComponent(val);
66-
matches.params[param] = val;
67-
if (!(param in matches)) matches[param] = val;
66+
matches.pathParams[pathParam] = val;
67+
if (!(pathParam in matches)) matches[pathParam] = val;
6868
if (rest) break;
6969
}
7070
return matches;
@@ -74,19 +74,22 @@ export function LocationProvider(props) {
7474
const [url, route] = useReducer(UPDATE, location.pathname + location.search);
7575
const wasPush = push === true;
7676

77+
/** @type {import('./router.d.ts').LocationHook} */
7778
const value = useMemo(() => {
7879
const u = new URL(url, location.origin);
7980
const path = u.pathname.replace(/\/+$/g, '') || '/';
80-
// @ts-ignore-next
81+
8182
return {
8283
url,
8384
path,
84-
query: Object.fromEntries(u.searchParams),
85+
pathParams: {},
86+
searchParams: Object.fromEntries(u.searchParams),
8587
route: (url, replace) => route({ url, replace }),
8688
wasPush
8789
};
8890
}, [url]);
8991

92+
9093
useLayoutEffect(() => {
9194
addEventListener('click', route);
9295
addEventListener('popstate', route);
@@ -97,7 +100,6 @@ export function LocationProvider(props) {
97100
};
98101
}, []);
99102

100-
// @ts-ignore
101103
return h(LocationProvider.ctx.Provider, { value }, props.children);
102104
}
103105

@@ -106,8 +108,7 @@ const RESOLVED = Promise.resolve();
106108
export function Router(props) {
107109
const [c, update] = useReducer(c => c + 1, 0);
108110

109-
const { url, query, wasPush, path } = useLocation();
110-
const { rest = path, params = {} } = useContext(RouteContext);
111+
const { url, path, pathParams, searchParams, wasPush } = useLocation();
111112

112113
const isLoading = useRef(false);
113114
const prevRoute = useRef(path);
@@ -129,7 +130,7 @@ export function Router(props) {
129130

130131
let pathRoute, defaultRoute, matchProps;
131132
toChildArray(props.children).some((/** @type {VNode<any>} */ vnode) => {
132-
const matches = exec(rest, vnode.props.path, (matchProps = { ...vnode.props, path: rest, query, params, rest: '' }));
133+
const matches = exec(path, vnode.props.path, (matchProps = { ...vnode.props, path, pathParams, searchParams, rest: '' }));
133134
if (matches) return (pathRoute = cloneElement(vnode, matchProps));
134135
if (vnode.props.default) defaultRoute = cloneElement(vnode, matchProps);
135136
});
@@ -140,7 +141,7 @@ export function Router(props) {
140141
prev.current = cur.current;
141142

142143
// Only mark as an update if the route component changed.
143-
const outgoing = prev.current && prev.current.props.children;
144+
const outgoing = prev.current;
144145
if (!outgoing || !incoming || incoming.type !== outgoing.type || incoming.props.component !== outgoing.props.component) {
145146
// This hack prevents Preact from diffing when we swap `cur` to `prev`:
146147
if (this.__v && this.__v.__k) this.__v.__k.reverse();
@@ -152,7 +153,8 @@ export function Router(props) {
152153
const isHydratingSuspense = cur.current && cur.current.__u & MODE_HYDRATE && cur.current.__u & MODE_SUSPENDED;
153154
const isHydratingBool = cur.current && cur.current.__h;
154155
// @ts-ignore
155-
cur.current = /** @type {VNode<any>} */ (h(RouteContext.Provider, { value: matchProps }, incoming));
156+
// TODO: Figure out how to set `.__h` properly so that it's preserved for the next render.
157+
cur.current = h(Fragment, {}, incoming);
156158
if (isHydratingSuspense) {
157159
cur.current.__u |= MODE_HYDRATE;
158160
cur.current.__u |= MODE_SUSPENDED;
@@ -254,11 +256,7 @@ Router.Provider = LocationProvider;
254256
LocationProvider.ctx = createContext(
255257
/** @type {import('./router.d.ts').LocationHook & { wasPush: boolean }} */ ({})
256258
);
257-
const RouteContext = createContext(
258-
/** @type {import('./router.d.ts').RouteHook & { rest: string }} */ ({})
259-
);
260259

261260
export const Route = props => h(props.component, props);
262261

263262
export const useLocation = () => useContext(LocationProvider.ctx);
264-
export const useRoute = () => useContext(RouteContext);

0 commit comments

Comments
 (0)