Socket
Socket
Sign inDemoInstall

@solidjs/router

Package Overview
Dependencies
Maintainers
2
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@solidjs/router - npm Package Compare versions

Comparing version 0.9.1 to 0.10.0-beta.0

dist/data/action.d.ts

28

dist/components.d.ts
import type { Component, JSX } from "solid-js";
import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteDataFunc, RouteDefinition, RouterIntegration } from "./types";
import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteLoadFunc, RouterIntegration, RouteSectionProps } from "./types";
declare module "solid-js" {

@@ -9,3 +9,3 @@ namespace JSX {

replace?: boolean;
link?: boolean;
preload?: boolean;
}

@@ -16,5 +16,4 @@ }

base?: string;
data?: RouteDataFunc;
root?: Component<RouteSectionProps>;
children: JSX.Element;
out?: object;
} & ({

@@ -28,23 +27,10 @@ url?: never;

export declare const Router: (props: RouterProps) => JSX.Element;
export interface RoutesProps {
base?: string;
children: JSX.Element;
}
export declare const Routes: (props: RoutesProps) => JSX.Element;
export declare const useRoutes: (routes: RouteDefinition | RouteDefinition[] | Readonly<RouteDefinition[]>, base?: string) => () => JSX.Element;
export type RouteProps<S extends string> = {
path: S | S[];
path?: S | S[];
children?: JSX.Element;
data?: RouteDataFunc;
load?: RouteLoadFunc;
matchFilters?: MatchFilters<S>;
} & ({
element?: never;
component: Component;
} | {
component?: never;
element?: JSX.Element;
preload?: () => void;
});
component?: Component;
};
export declare const Route: <S extends string>(props: RouteProps<S>) => JSX.Element;
export declare const Outlet: () => JSX.Element;
export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {

@@ -51,0 +37,0 @@ href: string;

@@ -5,7 +5,7 @@ /*@refresh skip*/

import { pathIntegration, staticIntegration } from "./integration";
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath, useRoute, useRouter } from "./routing";
import { joinPaths, normalizePath, createMemoObject } from "./utils";
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath } from "./routing";
import { normalizePath, createMemoObject } from "./utils";
export const Router = (props) => {
let e;
const { source, url, base, data, out } = props;
const { source, url, base } = props;
const integration = source ||

@@ -15,11 +15,16 @@ (isServer

: pathIntegration());
const routerState = createRouterContext(integration, base, data, out);
return (<RouterContextObj.Provider value={routerState}>{props.children}</RouterContextObj.Provider>);
const routeDefs = children(() => props.root
? {
component: props.root,
children: props.children
}
: props.children);
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
const routerState = createRouterContext(integration, branches, base);
return (<RouterContextObj.Provider value={routerState}>
<Routes routerState={routerState} branches={branches()}/>
</RouterContextObj.Provider>);
};
export const Routes = (props) => {
const router = useRouter();
const parentRoute = useRoute();
const routeDefs = children(() => props.children);
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
function Routes(props) {
const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
const params = createMemoObject(() => {

@@ -33,10 +38,2 @@ const m = matches();

});
if (router.out) {
router.out.matches.push(matches().map(({ route, path, params }) => ({
originalPath: route.originalPath,
pattern: route.pattern,
path,
params
})));
}
const disposers = [];

@@ -60,3 +57,3 @@ let root;

disposers[i] = dispose;
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i], params);
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => matches()[i], params);
});

@@ -75,6 +72,8 @@ }

</Show>);
}
const createOutlet = (child) => {
return () => (<Show when={child()} keyed>
{child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
</Show>);
};
export const useRoutes = (routes, base) => {
return () => <Routes base={base}>{routes}</Routes>;
};
export const Route = (props) => {

@@ -88,8 +87,2 @@ const childRoutes = children(() => props.children);

};
export const Outlet = () => {
const route = useRoute();
return (<Show when={route.child} keyed>
{child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
</Show>);
};
export function A(props) {

@@ -116,3 +109,3 @@ props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);

});
return (<a link {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
...(props.class && { [props.class]: true }),

@@ -119,0 +112,0 @@ [props.inactiveClass]: !isActive(),

export * from "./components";
export * from "./integration";
export * from "./lifecycle";
export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
export { mergeSearchString as _mergeSearchString } from "./utils";
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteDataFunc, RouteDataFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
export * from "./data";
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
import { isServer, delegateEvents, getRequestEvent, createComponent as createComponent$1, spread, mergeProps as mergeProps$1, template } from 'solid-js/web';
import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, createComponent, on, startTransition, resetErrorBoundaries, children, createRoot, Show, mergeProps, splitProps } from 'solid-js';
import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, on, startTransition, createComponent, resetErrorBoundaries, children, createRoot, Show, mergeProps, splitProps, createResource, sharedConfig, $TRACK } from 'solid-js';
import { createStore, reconcile } from 'solid-js/store';

@@ -204,2 +205,3 @@ function bindEvent(target, type, handler) {

const trimPathRegex = /^\/+|(\/)\/+$/g;
const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]);
function normalizePath(path, omitSlash = false) {

@@ -357,4 +359,3 @@ const s = path.replace(trimPathRegex, "$1");

const useRouter = () => invariant(useContext(RouterContextObj), "Make sure your app is wrapped in a <Router />");
let TempRoute;
const useRoute = () => TempRoute || useContext(RouteContextObj) || useRouter().base;
const useRoute = () => useContext(RouteContextObj) || useRouter().base;
const useResolvedPath = path => {

@@ -385,3 +386,2 @@ const route = useRoute();

const useParams = () => useRoute().params;
const useRouteData = () => useRoute().data;
const useSearchParams = () => {

@@ -408,6 +408,6 @@ const location = useLocation();

};
function createRoutes(routeDef, base = "", fallback) {
function createRoutes(routeDef, base = "") {
const {
component,
data,
load,
children

@@ -418,10 +418,4 @@ } = routeDef;

key: routeDef,
element: component ? () => createComponent(component, {}) : () => {
const {
element
} = routeDef;
return element === undefined && fallback ? createComponent(fallback, {}) : element;
},
preload: routeDef.component ? component.preload : routeDef.preload,
data
component,
load
};

@@ -466,8 +460,9 @@ return asArray(routeDef.path).reduce((acc, path) => {

}
function createBranches(routeDef, base = "", fallback, stack = [], branches = []) {
function createBranches(routeDef, base = "", stack = [], branches = []) {
const routeDefs = asArray(routeDef);
for (let i = 0, len = routeDefs.length; i < len; i++) {
const def = routeDefs[i];
if (def && typeof def === "object" && def.hasOwnProperty("path")) {
const routes = createRoutes(def, base, fallback);
if (def && typeof def === "object") {
if (!def.hasOwnProperty("path")) def.path = "";
const routes = createRoutes(def, base);
for (const route of routes) {

@@ -477,3 +472,3 @@ stack.push(route);

if (def.children && !isEmptyArray) {
createBranches(def.children, route.pattern, fallback, stack, branches);
createBranches(def.children, route.pattern, stack, branches);
} else {

@@ -536,3 +531,11 @@ const branch = createBranch([...stack], branches.length);

}
function createRouterContext(integration, base = "", data, out) {
const actions = new Map();
function registerAction(url, fn) {
actions.set(url, fn);
}
let intent;
function getIntent() {
return intent;
}
function createRouterContext(integration, getBranches, base = "") {
const {

@@ -545,7 +548,4 @@ signal: [source, setSource],

const beforeLeave = utils.beforeLeave || createBeforeLeave();
let submissions = [];
const basePath = resolvePath("", base);
const output = isServer && out ? Object.assign(out, {
matches: [],
url: undefined
}) : undefined;
if (basePath === undefined) {

@@ -582,15 +582,2 @@ throw new Error(`${basePath} is not a valid base path`);

};
if (data) {
try {
TempRoute = baseRoute;
baseRoute.data = data({
data: undefined,
params: {},
location,
navigate: navigatorFactory(baseRoute)
});
} finally {
TempRoute = undefined;
}
}
function navigateFromRoute(route, to, options) {

@@ -627,5 +614,2 @@ // Untrack in case someone navigates in an effect - don't want to track `reference` or route paths

if (isServer) {
if (output) {
output.url = resolvedTo;
}
const e = getRequestEvent();

@@ -647,2 +631,3 @@ e && (e.response = Response.redirect(resolvedTo, 302));

start(() => {
intent = "navigate";
setReference(resolvedTo);

@@ -653,2 +638,3 @@ setState(nextState);

if (referrers.length === len) {
intent = undefined;
navigateEnd({

@@ -691,4 +677,7 @@ value: resolvedTo,

start(() => {
intent = "native";
setReference(value);
setState(state);
}).then(() => {
intent = undefined;
});

@@ -699,12 +688,24 @@ }

if (!isServer) {
function handleAnchorClick(evt) {
let preloadTimeout = {};
function isSvg(el) {
return el.namespaceURI === "http://www.w3.org/2000/svg";
}
function handleAnchor(evt) {
if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
const a = evt.composedPath().find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
if (!a || !a.hasAttribute("link")) return;
const href = a.href;
if (a.target || !href && !a.hasAttribute("state")) return;
if (!a) return;
const svg = isSvg(a);
const href = svg ? a.href.baseVal : a.href;
const target = svg ? a.target.baseVal : a.target;
if (target || !href && !a.hasAttribute("state")) return;
const rel = (a.getAttribute("rel") || "").split(/\s+/);
if (a.hasAttribute("download") || rel && rel.includes("external")) return;
const url = new URL(href);
const url = svg ? new URL(href, document.baseURI) : new URL(href);
if (url.origin !== window.location.origin || basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
return [a, url];
}
function handleAnchorClick(evt) {
const res = handleAnchor(evt);
if (!res) return;
const [a, url] = res;
const to = parsePath(url.pathname + url.search + url.hash);

@@ -720,11 +721,87 @@ const state = a.getAttribute("state");

}
function doPreload(a, path) {
const preload = a.getAttribute("preload") !== "false";
const matches = getRouteMatches(getBranches(), path);
const prevIntent = intent;
intent = "preload";
for (let match in matches) {
const {
route,
params
} = matches[match];
route.component && route.component.preload && route.component.preload();
preload && route.load && route.load({
params,
location
});
}
intent = prevIntent;
}
function handleAnchorPreload(evt) {
const res = handleAnchor(evt);
if (!res) return;
const [a, url] = res;
if (!preloadTimeout[url.pathname]) doPreload(a, url.pathname);
}
function handleAnchorIn(evt) {
const res = handleAnchor(evt);
if (!res) return;
const [a, url] = res;
if (preloadTimeout[url.pathname]) return;
preloadTimeout[url.pathname] = setTimeout(() => {
doPreload(a, url.pathname);
delete preloadTimeout[url.pathname];
}, 50);
}
function handleAnchorOut(evt) {
const res = handleAnchor(evt);
if (!res) return;
const [, url] = res;
if (preloadTimeout[url.pathname]) {
clearTimeout(preloadTimeout[url.pathname]);
delete preloadTimeout[url.pathname];
}
}
function handleFormSubmit(evt) {
let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
if (actionRef && actionRef.startsWith("action:")) {
const data = new FormData(evt.target);
actions.get(actionRef.slice(7))(data);
evt.preventDefault();
}
}
// ensure delegated events run first
delegateEvents(["click"]);
// ensure delegated event run first
delegateEvents(["click", "submit"]);
document.addEventListener("click", handleAnchorClick);
onCleanup(() => document.removeEventListener("click", handleAnchorClick));
document.addEventListener("mouseover", handleAnchorIn);
document.addEventListener("mouseout", handleAnchorOut);
document.addEventListener("focusin", handleAnchorPreload);
document.addEventListener("touchstart", handleAnchorPreload);
document.addEventListener("submit", handleFormSubmit);
onCleanup(() => {
document.removeEventListener("click", handleAnchorClick);
document.removeEventListener("mouseover", handleAnchorIn);
document.removeEventListener("mouseout", handleAnchorOut);
document.removeEventListener("focusin", handleAnchorPreload);
document.removeEventListener("touchstart", handleAnchorPreload);
document.removeEventListener("submit", handleFormSubmit);
});
} else {
function initFromFlash(params) {
let param = params.form ? JSON.parse(params.form) : null;
if (!param || !param.result) {
return [];
}
const input = new Map(param.entries);
return [{
url: param.url,
result: param.error ? new Error(param.result.message) : param.result,
input: input
}];
}
submissions = initFromFlash(location.query);
}
return {
base: baseRoute,
out: output,
location,

@@ -735,29 +812,29 @@ isRouting,

navigatorFactory,
beforeLeave
beforeLeave,
submissions: createSignal(submissions)
};
}
function createRouteContext(router, parent, child, match, params) {
function createRouteContext(router, parent, outlet, match, params) {
const {
base,
location,
navigatorFactory
location
} = router;
const {
pattern,
element: outlet,
preload,
data
component,
load
} = match().route;
const path = createMemo(() => match().path);
preload && preload();
const route = {
parent,
pattern,
get child() {
return child();
},
path,
params,
data: parent.data,
outlet,
outlet: () => component ? createComponent(component, {
params,
location,
get children() {
return outlet();
}
}) : outlet(),
resolvePath(to) {

@@ -767,19 +844,11 @@ return resolvePath(base.path(), to, path());

};
if (data) {
try {
TempRoute = route;
route.data = data({
data: parent.data,
params,
location,
navigate: navigatorFactory(route)
});
} finally {
TempRoute = undefined;
}
}
component && component.preload && component.preload();
load && load({
params,
location
});
return route;
}
const _tmpl$ = /*#__PURE__*/template(`<a link>`);
const _tmpl$ = /*#__PURE__*/template(`<a></a>`, 2);
const Router = props => {

@@ -790,5 +859,3 @@ let e;

url,
base,
data,
out
base
} = props;

@@ -798,16 +865,22 @@ const integration = source || (isServer ? staticIntegration({

}) : pathIntegration());
const routerState = createRouterContext(integration, base, data, out);
const routeDefs = children(() => props.root ? {
component: props.root,
children: props.children
} : props.children);
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
const routerState = createRouterContext(integration, branches, base);
return createComponent$1(RouterContextObj.Provider, {
value: routerState,
get children() {
return props.children;
return createComponent$1(Routes, {
routerState: routerState,
get branches() {
return branches();
}
});
}
});
};
const Routes = props => {
const router = useRouter();
const parentRoute = useRoute();
const routeDefs = children(() => props.children);
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
function Routes(props) {
const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
const params = createMemoObject(() => {

@@ -821,14 +894,2 @@ const m = matches();

});
if (router.out) {
router.out.matches.push(matches().map(({
route,
path,
params
}) => ({
originalPath: route.originalPath,
pattern: route.pattern,
path,
params
})));
}
const disposers = [];

@@ -851,3 +912,3 @@ let root;

disposers[i] = dispose;
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i], params);
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => matches()[i], params);
});

@@ -875,22 +936,7 @@ }

});
};
const useRoutes = (routes, base) => {
return () => createComponent$1(Routes, {
base: base,
children: routes
});
};
const Route = props => {
const childRoutes = children(() => props.children);
return mergeProps(props, {
get children() {
return childRoutes();
}
});
};
const Outlet = () => {
const route = useRoute();
return createComponent$1(Show, {
}
const createOutlet = child => {
return () => createComponent$1(Show, {
get when() {
return route.child;
return child();
},

@@ -906,2 +952,10 @@ keyed: true,

};
const Route = props => {
const childRoutes = children(() => props.children);
return mergeProps(props, {
get children() {
return childRoutes();
}
});
};
function A(props) {

@@ -924,3 +978,3 @@ props = mergeProps({

return (() => {
const _el$ = _tmpl$();
const _el$ = _tmpl$.cloneNode(true);
spread(_el$, mergeProps$1(rest, {

@@ -968,2 +1022,208 @@ get href() {

export { A, A as Link, A as NavLink, Navigate, Outlet, Route, Router, Routes, mergeSearchString as _mergeSearchString, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useRouteData, useRoutes, useSearchParams };
/**
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
*/
function createAsync(fn) {
const [resource] = createResource(() => fn(), v => v);
return () => resource();
}
const LocationHeader = "Location";
const PRELOAD_TIMEOUT = 5000;
let cacheMap = new Map();
function getCache() {
if (!isServer) return cacheMap;
const req = getRequestEvent() || sharedConfig.context;
return req.routerCache || (req.routerCache = new Map());
}
function revalidate(key) {
return startTransition(() => {
const now = Date.now();
for (let k of cacheMap.keys()) {
if (key === undefined || k === key) {
const set = cacheMap.get(k)[3];
revalidateSignals(set, now);
cacheMap.delete(k);
}
}
});
}
function revalidateSignals(set, time) {
for (let s of set) s[1](time);
}
function cache(fn, name, options) {
const [store, setStore] = createStore({});
return (...args) => {
const cache = getCache();
const intent = getIntent();
const owner = getOwner();
const navigate = owner ? useNavigate() : undefined;
const now = Date.now();
const key = name + (args.length ? ":" + args.join(":") : "");
let cached = cache.get(key);
let version;
if (owner) {
version = createSignal(now, {
equals: (p, v) => v - p < 50 // margin of error
});
onCleanup(() => cached[3].delete(version));
version[0](); // track it;
}
if (cached && (isServer || intent === "native" || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
version && cached[3].add(version);
if (cached[2] === "preload" && intent !== "preload") {
cached[0] = now;
cached[1] = "then" in cached[1] ? cached[1].then(handleResponse) : handleResponse(cached[1]);
cached[2] = intent;
}
if (!isServer && intent === "navigate") {
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
}
return cached[1];
}
let res = fn(...args);
if (intent !== "preload") {
res = "then" in res ? res.then(handleResponse) : handleResponse(res);
}
if (cached) {
cached[0] = now;
cached[1] = res;
cached[2] = intent;
version && cached[3].add(version);
if (!isServer && intent === "navigate") {
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
}
} else cache.set(key, cached = [now, res, intent, new Set(version ? [version] : [])]);
return res;
function handleRedirect(response) {
startTransition(() => {
let url = response.headers.get(LocationHeader);
if (url && url.startsWith("/")) {
navigate(url, {
replace: true
});
} else if (!isServer && url) {
window.location.href = url;
}
});
}
function handleResponse(v) {
if (v instanceof Response && redirectStatusCodes.has(v.status)) {
if (navigate) isServer ? handleRedirect(v) : setTimeout(() => handleRedirect(v), 0);
return;
}
setStore(key, reconcile(v, options));
return store[key];
}
};
}
function useSubmissions(fn, filter) {
const router = useRouter();
const subs = createMemo(() => router.submissions[0]().filter(s => s.url === fn.toString() && (!filter || filter(s.input))));
return new Proxy([], {
get(_, property) {
if (property === $TRACK) return subs();
if (property === "pending") return subs().some(sub => !sub.result);
return subs()[property];
}
});
}
function useSubmission(fn, filter) {
const submissions = useSubmissions(fn, filter);
return {
get clear() {
return submissions[0]?.clear;
},
get retry() {
return submissions[0]?.retry;
},
get url() {
return submissions[0]?.url;
},
get input() {
return submissions[0]?.input;
},
get result() {
return submissions[0]?.result;
},
get pending() {
return submissions[0]?.pending;
}
};
}
function action(fn, name) {
function mutate(variables) {
const p = fn(variables);
const [result, setResult] = createSignal();
let submission;
const router = this;
router.submissions[1](s => [...s, submission = {
input: variables,
url,
get result() {
return result()?.data;
},
get pending() {
return !result();
},
clear() {
router.submissions[1](v => v.filter(i => i.input !== variables));
},
retry() {
setResult(undefined);
const p = fn(variables);
p.then(async data => {
const keys = handleResponse(data, router.navigatorFactory());
await revalidate(keys);
data ? setResult({
data
}) : submission.clear();
return data;
}).catch(error => {
setResult({
data: error
});
});
return p;
}
}]);
p.then(async data => {
const keys = handleResponse(data, router.navigatorFactory());
await revalidate(keys);
data ? setResult({
data
}) : submission.clear();
return data;
}).catch(error => {
setResult({
data: error
});
});
return p;
}
const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
mutate.toString = () => {
if (!url) throw new Error("Client Actions need explicit names if server rendered");
return url;
};
if (!isServer) registerAction(url, mutate);
return mutate;
}
function handleResponse(response, navigate) {
if (response instanceof Response && redirectStatusCodes.has(response.status)) {
const locationUrl = response.headers.get("Location") || "/";
if (locationUrl.startsWith("http")) {
window.location.href = locationUrl;
} else {
navigate(locationUrl);
}
}
// return keys
return;
}
export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, revalidate, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
export * from "./components";
export * from "./integration";
export * from "./lifecycle";
export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
export { mergeSearchString as _mergeSearchString } from "./utils";
export * from "./data";

@@ -1,3 +0,3 @@

import type { Component, Accessor } from "solid-js";
import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDataFunc, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
import type { JSX, Accessor } from "solid-js";
import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;

@@ -14,13 +14,12 @@ export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;

export declare const useParams: <T extends Params>() => T;
type MaybeReturnType<T> = T extends (...args: any) => infer R ? R : T;
export declare const useRouteData: <T>() => MaybeReturnType<T>;
export declare const useSearchParams: <T extends Params>() => [T, (params: SetParams, options?: Partial<NavigateOptions>) => void];
export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
export declare function createRoutes(routeDef: RouteDefinition, base?: string, fallback?: Component): Route[];
export declare function createRoutes(routeDef: RouteDefinition, base?: string): Route[];
export declare function createBranch(routes: Route[], index?: number): Branch;
export declare function createBranches(routeDef: RouteDefinition | RouteDefinition[], base?: string, fallback?: Component, stack?: Route[], branches?: Branch[]): Branch[];
export declare function createBranches(routeDef: RouteDefinition | RouteDefinition[], base?: string, stack?: Route[], branches?: Branch[]): Branch[];
export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
export declare function createLocation(path: Accessor<string>, state: Accessor<any>): Location;
export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, base?: string, data?: RouteDataFunc, out?: object): RouterContext;
export declare function createRouteContext(router: RouterContext, parent: RouteContext, child: () => RouteContext, match: () => RouteMatch, params: Params): RouteContext;
export {};
export declare function registerAction(url: string, fn: Function): void;
export declare function getIntent(): "native" | "navigate" | "preload" | undefined;
export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, getBranches?: () => Branch[], base?: string): RouterContext;
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext;

@@ -38,3 +38,2 @@ import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js";

export const useParams = () => useRoute().params;
export const useRouteData = () => useRoute().data;
export const useSearchParams = () => {

@@ -61,19 +60,9 @@ const location = useLocation();

};
export function createRoutes(routeDef, base = "", fallback) {
const { component, data, children } = routeDef;
export function createRoutes(routeDef, base = "") {
const { component, load, children } = routeDef;
const isLeaf = !children || (Array.isArray(children) && !children.length);
const shared = {
key: routeDef,
element: component
? () => createComponent(component, {})
: () => {
const { element } = routeDef;
return element === undefined && fallback
? createComponent(fallback, {})
: element;
},
preload: routeDef.component
? component.preload
: routeDef.preload,
data
component,
load
};

@@ -118,8 +107,10 @@ return asArray(routeDef.path).reduce((acc, path) => {

}
export function createBranches(routeDef, base = "", fallback, stack = [], branches = []) {
export function createBranches(routeDef, base = "", stack = [], branches = []) {
const routeDefs = asArray(routeDef);
for (let i = 0, len = routeDefs.length; i < len; i++) {
const def = routeDefs[i];
if (def && typeof def === "object" && def.hasOwnProperty("path")) {
const routes = createRoutes(def, base, fallback);
if (def && typeof def === "object") {
if (!def.hasOwnProperty("path"))
def.path = "";
const routes = createRoutes(def, base);
for (const route of routes) {

@@ -129,3 +120,3 @@ stack.push(route);

if (def.children && !isEmptyArray) {
createBranches(def.children, route.pattern, fallback, stack, branches);
createBranches(def.children, route.pattern, stack, branches);
}

@@ -189,3 +180,11 @@ else {

}
export function createRouterContext(integration, base = "", data, out) {
const actions = new Map();
export function registerAction(url, fn) {
actions.set(url, fn);
}
let intent;
export function getIntent() {
return intent;
}
export function createRouterContext(integration, getBranches, base = "") {
const { signal: [source, setSource], utils = {} } = normalizeIntegration(integration);

@@ -195,9 +194,4 @@ const parsePath = utils.parsePath || (p => p);

const beforeLeave = utils.beforeLeave || createBeforeLeave();
let submissions = [];
const basePath = resolvePath("", base);
const output = isServer && out
? Object.assign(out, {
matches: [],
url: undefined
})
: undefined;
if (basePath === undefined) {

@@ -232,16 +226,2 @@ throw new Error(`${basePath} is not a valid base path`);

};
if (data) {
try {
TempRoute = baseRoute;
baseRoute.data = data({
data: undefined,
params: {},
location,
navigate: navigatorFactory(baseRoute)
});
}
finally {
TempRoute = undefined;
}
}
function navigateFromRoute(route, to, options) {

@@ -278,5 +258,2 @@ // Untrack in case someone navigates in an effect - don't want to track `reference` or route paths

if (isServer) {
if (output) {
output.url = resolvedTo;
}
const e = getRequestEvent();

@@ -289,2 +266,3 @@ e && (e.response = Response.redirect(resolvedTo, 302));

start(() => {
intent = "navigate";
setReference(resolvedTo);

@@ -295,2 +273,3 @@ setState(nextState);

if (referrers.length === len) {
intent = undefined;
navigateEnd({

@@ -330,4 +309,7 @@ value: resolvedTo,

start(() => {
intent = "native";
setReference(value);
setState(state);
}).then(() => {
intent = undefined;
});

@@ -338,3 +320,7 @@ }

if (!isServer) {
function handleAnchorClick(evt) {
let preloadTimeout = {};
function isSvg(el) {
return el.namespaceURI === "http://www.w3.org/2000/svg";
}
function handleAnchor(evt) {
if (evt.defaultPrevented ||

@@ -350,6 +336,8 @@ evt.button !== 0 ||

.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
if (!a || !a.hasAttribute("link"))
if (!a)
return;
const href = a.href;
if (a.target || (!href && !a.hasAttribute("state")))
const svg = isSvg(a);
const href = svg ? a.href.baseVal : a.href;
const target = svg ? a.target.baseVal : a.target;
if (target || (!href && !a.hasAttribute("state")))
return;

@@ -359,6 +347,13 @@ const rel = (a.getAttribute("rel") || "").split(/\s+/);

return;
const url = new URL(href);
const url = svg ? new URL(href, document.baseURI) : new URL(href);
if (url.origin !== window.location.origin ||
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
return;
return [a, url];
}
function handleAnchorClick(evt) {
const res = handleAnchor(evt);
if (!res)
return;
const [a, url] = res;
const to = parsePath(url.pathname + url.search + url.hash);

@@ -374,10 +369,89 @@ const state = a.getAttribute("state");

}
// ensure delegated events run first
delegateEvents(["click"]);
function doPreload(a, path) {
const preload = a.getAttribute("preload") !== "false";
const matches = getRouteMatches(getBranches(), path);
const prevIntent = intent;
intent = "preload";
for (let match in matches) {
const { route, params } = matches[match];
route.component &&
route.component.preload &&
route.component.preload();
preload && route.load && route.load({ params, location });
}
intent = prevIntent;
}
function handleAnchorPreload(evt) {
const res = handleAnchor(evt);
if (!res)
return;
const [a, url] = res;
if (!preloadTimeout[url.pathname])
doPreload(a, url.pathname);
}
function handleAnchorIn(evt) {
const res = handleAnchor(evt);
if (!res)
return;
const [a, url] = res;
if (preloadTimeout[url.pathname])
return;
preloadTimeout[url.pathname] = setTimeout(() => {
doPreload(a, url.pathname);
delete preloadTimeout[url.pathname];
}, 50);
}
function handleAnchorOut(evt) {
const res = handleAnchor(evt);
if (!res)
return;
const [, url] = res;
if (preloadTimeout[url.pathname]) {
clearTimeout(preloadTimeout[url.pathname]);
delete preloadTimeout[url.pathname];
}
}
function handleFormSubmit(evt) {
let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
if (actionRef && actionRef.startsWith("action:")) {
const data = new FormData(evt.target);
actions.get(actionRef.slice(7))(data);
evt.preventDefault();
}
}
;
// ensure delegated event run first
delegateEvents(["click", "submit"]);
document.addEventListener("click", handleAnchorClick);
onCleanup(() => document.removeEventListener("click", handleAnchorClick));
document.addEventListener("mouseover", handleAnchorIn);
document.addEventListener("mouseout", handleAnchorOut);
document.addEventListener("focusin", handleAnchorPreload);
document.addEventListener("touchstart", handleAnchorPreload);
document.addEventListener("submit", handleFormSubmit);
onCleanup(() => {
document.removeEventListener("click", handleAnchorClick);
document.removeEventListener("mouseover", handleAnchorIn);
document.removeEventListener("mouseout", handleAnchorOut);
document.removeEventListener("focusin", handleAnchorPreload);
document.removeEventListener("touchstart", handleAnchorPreload);
document.removeEventListener("submit", handleFormSubmit);
});
}
else {
function initFromFlash(params) {
let param = params.form ? JSON.parse(params.form) : null;
if (!param || !param.result) {
return [];
}
const input = new Map(param.entries);
return [{
url: param.url,
result: param.error ? new Error(param.result.message) : param.result,
input: input
}];
}
submissions = initFromFlash(location.query);
}
return {
base: baseRoute,
out: output,
location,

@@ -388,20 +462,24 @@ isRouting,

navigatorFactory,
beforeLeave
beforeLeave,
submissions: createSignal(submissions)
};
}
export function createRouteContext(router, parent, child, match, params) {
const { base, location, navigatorFactory } = router;
const { pattern, element: outlet, preload, data } = match().route;
export function createRouteContext(router, parent, outlet, match, params) {
const { base, location } = router;
const { pattern, component, load } = match().route;
const path = createMemo(() => match().path);
preload && preload();
const route = {
parent,
pattern,
get child() {
return child();
},
path,
params,
data: parent.data,
outlet,
outlet: () => component
? createComponent(component, {
params,
location,
get children() {
return outlet();
}
})
: outlet(),
resolvePath(to) {

@@ -411,12 +489,7 @@ return resolvePath(base.path(), to, path());

};
if (data) {
try {
TempRoute = route;
route.data = data({ data: parent.data, params, location, navigate: navigatorFactory(route) });
}
finally {
TempRoute = undefined;
}
}
component &&
component.preload &&
component.preload();
load && load({ params, location });
return route;
}

@@ -1,5 +0,6 @@

import { Component, JSX } from "solid-js";
import { Component, JSX, Signal } from "solid-js";
declare module "solid-js/web" {
interface RequestEvent {
response?: Response;
routerCache?: Map<any, any>;
}

@@ -41,22 +42,17 @@ }

}
export interface RouteDataFuncArgs<T = unknown> {
data: T extends RouteDataFunc<infer _, infer R> ? R : T;
export interface RouteLoadFuncArgs {
params: Params;
location: Location;
navigate: Navigator;
}
export type RouteDataFunc<T = unknown, R = unknown> = (args: RouteDataFuncArgs<T>) => R;
export type RouteLoadFunc = (args: RouteLoadFuncArgs) => void;
export interface RouteSectionProps extends RouteLoadFuncArgs {
children?: JSX.Element;
}
export type RouteDefinition<S extends string | string[] = any> = {
path: S;
matchFilters?: MatchFilters<S>;
data?: RouteDataFunc;
load?: RouteLoadFunc;
children?: RouteDefinition | RouteDefinition[];
} & ({
element?: never;
component: Component;
} | {
component?: never;
element?: JSX.Element;
preload?: () => void;
});
component?: Component<RouteSectionProps>;
};
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);

@@ -84,5 +80,4 @@ export type PathParams<P extends string | readonly string[]> = P extends `${infer Head}/${infer Tail}` ? [...PathParams<Head>, ...PathParams<Tail>] : P extends `:${infer S}?` ? [S] : P extends `:${infer S}` ? [S] : P extends `*${infer S}` ? [S] : [];

pattern: string;
element: () => JSX.Element;
preload?: () => void;
data?: RouteDataFunc;
component?: Component<RouteSectionProps>;
load?: RouteLoadFunc;
matcher: (location: string) => PathMatch | null;

@@ -99,3 +94,2 @@ matchFilters?: MatchFilters;

child?: RouteContext;
data?: unknown;
pattern: string;

@@ -125,3 +119,2 @@ params: Params;

base: RouteContext;
out?: RouterOutput;
location: Location;

@@ -133,2 +126,3 @@ navigatorFactory: NavigatorFactory;

beforeLeave: BeforeLeaveLifecycle;
submissions: Signal<Submission<any, any>[]>;
}

@@ -152,1 +146,9 @@ export interface BeforeLeaveEventArgs {

}
export type Submission<T, U> = {
readonly input: T;
readonly result?: U;
readonly pending: boolean;
readonly url: string;
clear: () => void;
retry: () => void;
};
import type { MatchFilters, Params, PathMatch, Route, SetParams } from "./types";
export declare const redirectStatusCodes: Set<number>;
export declare function normalizePath(path: string, omitSlash?: boolean): string;

@@ -3,0 +4,0 @@ export declare function resolvePath(base: string, path: string, from?: string): string | undefined;

import { createMemo, getOwner, runWithOwner } from "solid-js";
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
const trimPathRegex = /^\/+|(\/)\/+$/g;
export const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]);
export function normalizePath(path, omitSlash = false) {

@@ -5,0 +6,0 @@ const s = path.replace(trimPathRegex, "$1");

@@ -9,3 +9,3 @@ {

"license": "MIT",
"version": "0.9.1",
"version": "0.10.0-beta.0",
"homepage": "https://github.com/solidjs/solid-router#readme",

@@ -39,3 +39,3 @@ "repository": {

"@types/jest": "^29.0.0",
"@types/node": "^18.7.14",
"@types/node": "^20.9.0",
"babel-jest": "^29.0.1",

@@ -49,3 +49,3 @@ "babel-preset-solid": "^1.6.6",

"solid-js": "^1.8.4",
"typescript": "^4.9.4"
"typescript": "^5.2.2"
},

@@ -52,0 +52,0 @@ "peerDependencies": {

@@ -7,4 +7,2 @@ <p>

#### Note: v0.9.0 requires Solid 1.8.4 or later
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.

@@ -14,3 +12,3 @@

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

@@ -20,9 +18,10 @@ - [Getting Started](#getting-started)

- [Configure Your Routes](#configure-your-routes)
- [Create Links to Your Routes](#create-links-to-your-routes)
- [Create Links to Your Routes](#create-links-to-your-routes)
- [Dynamic Routes](#dynamic-routes)
- [Data Functions](#data-functions)
- [Nested Routes](#nested-routes)
- [Hash Mode Router](#hash-mode-router)
- [Memory Mode Router](#memory-mode-router)
- [Data APIs](#data-apis)
- [Config Based Routing](#config-based-routing)
- [Components](#components)
- [Router Primitives](#router-primitives)

@@ -36,3 +35,2 @@ - [useParams](#useparams)

- [useMatch](#usematch)
- [useRoutes](#useroutes)
- [useBeforeLeave](#usebeforeleave)

@@ -49,3 +47,3 @@ - [SPAs in Deployed Environments](#spas-in-deployed-environments)

Install `@solidjs/router`, then wrap your root component with the Router component:
Install `@solidjs/router`, then start your application by rendering the router component

@@ -55,10 +53,5 @@ ```jsx

import { Router } from "@solidjs/router";
import App from "./App";
render(
() => (
<Router>
<App />
</Router>
),
() => <Router />,
document.getElementById("app")

@@ -68,3 +61,3 @@ );

This sets up a context so that we can display the routes anywhere in the app.
This sets up a Router that will match on the url to display the desired page

@@ -75,21 +68,25 @@ ### Configure Your Routes

1. Use the `Routes` component to specify where the routes should appear in your app.
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.
```jsx
import { Routes, Route } from "@solidjs/router";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
export default function App() {
return (
<>
<h1>My Site with Lots of Pages</h1>
<Routes></Routes>
</>
);
}
import Home from "./pages/Home";
import Users from "./pages/Users";
render(() => (
<Router>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
</Router>
), document.getElementById("app"));
```
2. Provide a root level layout
2. Add each route using the `Route` component, specifying a path and an element or component to render when the user navigates to that path.
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
```jsx
import { Routes, Route } from "@solidjs/router";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";

@@ -99,17 +96,15 @@ import Home from "./pages/Home";

export default function App() {
return (
<>
<h1>My Site with Lots of Pages</h1>
<Routes>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
<Route
path="/about"
element={<div>This site was made with Solid</div>}
/>
</Routes>
</>
);
}
const App = props => (
<>
<h1>My Site with lots of pages</h1>
{props.children}
</>
)
render(() => (
<Router root={App}>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
</Router>
), document.getElementById("app"));
```

@@ -123,79 +118,52 @@

import { lazy } from "solid-js";
import { Routes, Route } from "@solidjs/router";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const Home = lazy(() => import("./pages/Home"));
export default function App() {
return (
<>
<h1>My Site with Lots of Pages</h1>
<Routes>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
<Route
path="/about"
element={<div>This site was made with Solid</div>}
/>
</Routes>
</>
);
}
const App = props => (
<>
<h1>My Site with lots of pages</h1>
{props.children}
</>
)
render(() => (
<Router root={App}>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
</Router>
), document.getElementById("app"));
```
## Create Links to Your Routes
### Create Links to Your Routes
Use the `A` component to create an anchor tag that takes you to a route:
Use an anchor tag that takes you to a route:
```jsx
import { lazy } from "solid-js";
import { Routes, Route, A } from "@solidjs/router";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const Home = lazy(() => import("./pages/Home"));
export default function App() {
return (
<>
<h1>My Site with Lots of Pages</h1>
<nav>
<A href="/about">About</A>
<A href="/">Home</A>
</nav>
<Routes>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
<Route
path="/about"
element={<div>This site was made with Solid</div>}
/>
</Routes>
</>
);
}
```
const App = props => (
<>
<nav>
<a href="/about">About</a>
<a href="/">Home</a>
</nav>
<h1>My Site with lots of pages</h1>
{props.children}
</>
);
The `<A>` tag also 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.
| prop | type | description |
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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. |
| noScroll | boolean | If true, turn off the default behavior of scrolling to the top of the new page |
| replace | boolean | If true, don't add a new entry to the browser history. (By default, the new page will be added to the browser history, so pressing the back button will take you to the previous route.) |
| state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating |
| inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) |
| activeClass | string | The class to show when the link is active |
| 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` |
### The Navigate Component
Solid Router provides a `Navigate` component that works similarly to `A`, but it will _immediately_ navigate to the provided path as soon as the component is rendered. It also uses the `href` prop, but you have the additional option of passing a function to `href` that returns a path to navigate to:
```jsx
function getPath({ navigate, location }) {
// navigate is the result of calling useNavigate(); location is the result of calling useLocation().
// You can use those to dynamically determine a path to navigate to
return "/some-path";
}
// Navigating to /redirect will redirect you to the result of getPath
<Route path="/redirect" element={<Navigate href={getPath} />} />;
render(() => (
<Router root={App}>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
</Router>
), document.getElementById("app"));
```

@@ -209,3 +177,5 @@

import { lazy } from "solid-js";
import { Routes, Route } from "@solidjs/router";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));

@@ -215,18 +185,9 @@ const User = lazy(() => import("./pages/User"));

export default function App() {
return (
<>
<h1>My Site with Lots of Pages</h1>
<Routes>
<Route path="/users" component={Users} />
<Route path="/users/:id" component={User} />
<Route path="/" component={Home} />
<Route
path="/about"
element={<div>This site was made with Solid</div>}
/>
</Routes>
</>
);
}
render(() => (
<Router>
<Route path="/users" component={Users} />
<Route path="/users/:id" component={User} />
<Route path="/" component={Home} />
</Router>
), document.getElementById("app"));
```

@@ -238,8 +199,7 @@

> **Note on Animation/Transitions**:
> 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:
> ```js
><Show when={params.something} keyed><MyComponent></Show>
>```
**Note on Animation/Transitions**:
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:
```jsx
<Show when={params.something} keyed><MyComponent></Show>
```
---

@@ -250,10 +210,9 @@

```tsx
```jsx
import { lazy } from "solid-js";
import { Routes, Route } from "@solidjs/router";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import type { SegmentValidators } from "./types";
const Users = lazy(() => import("./pages/Users"));
const User = lazy(() => import("./pages/User"));
const Home = lazy(() => import("./pages/Home"));

@@ -266,16 +225,11 @@ const filters: MatchFilters = {

export default function App() {
return (
<>
<h1>My Site with Lots of Pages</h1>
<Routes>
<Route
path="/users/:parent/:id/:withHtmlExtension"
component={User}
matchFilters={filters}
/>
</Routes>
</>
);
}
render(() => (
<Router>
<Route
path="/users/:parent/:id/:withHtmlExtension"
component={User}
matchFilters={filters}
/>
</Router>
), document.getElementById("app"));
```

@@ -296,15 +250,2 @@

```jsx
// async fetching function
import { fetchUser } ...
export default function User () {
const params = useParams();
const [userData] = createResource(() => params.id, fetchUser);
return <A href={userData.twitter}>{userData.name}</A>
}
```
### Optional Parameters

@@ -316,3 +257,3 @@

// Matches stories and stories/123 but not stories/123/comments
<Route path="/stories/:id?" element={<Stories />} />
<Route path="/stories/:id?" component={Stories} />
```

@@ -332,3 +273,3 @@

```jsx
<Route path="foo/*any" element={<div>{useParams().any}</div>} />
<Route path="foo/*any" component={Foo} />
```

@@ -347,59 +288,2 @@

## Data Functions
In the [above example](#dynamic-routes), the User component is lazy-loaded and then the data is fetched. With route data functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible.
To do this, create a function that fetches and returns the data using `createResource`. Then pass that function to the `data` prop of the `Route` component.
```js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
import { fetchUser } ...
const User = lazy(() => import("./pages/users/[id].js"));
// Data function
function UserData({params, location, navigate, data}) {
const [user] = createResource(() => params.id, fetchUser);
return user;
}
// Pass it in the route definition
<Route path="/users/:id" component={User} data={UserData} />;
```
When the route is loaded, the data function is called, and the result can be accessed by calling `useRouteData()` in the route component.
```jsx
// pages/users/[id].js
import { useRouteData } from "@solidjs/router";
export default function User() {
const user = useRouteData();
return <h1>{user().name}</h1>;
}
```
As its only argument, the data function is passed an object that you can use to access route information:
| key | type | description |
| -------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| params | object | The route parameters (same value as calling `useParams()` inside the route component) |
| location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
| navigate | `(to: string, options?: NavigateOptions) => void` | A function that you can call to navigate to a different route instead (corresponds to [`useNavigate()`](#usenavigate)) |
| data | unknown | The data returned by the [parent's](#nested-routes) data function, if any. (Data will pass through any intermediate nesting.) |
A common pattern is to export the data function that corresponds to a route in a dedicated `route.data.js` file. This way, the data function can be imported without loading anything else.
```js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
import { fetchUser } ...
import UserData from "./pages/users/[id].data.js";
const User = lazy(() => import("/pages/users/[id].js"));
// In the Route definition
<Route path="/users/:id" component={User} data={UserData} />;
```
## Nested Routes

@@ -440,12 +324,10 @@

You can also take advantage of nesting by adding a parent element with an `<Outlet/>`.
You can also take advantage of nesting by using `props.children` passed to the route component.
```jsx
import { Outlet } from "@solidjs/router";
function PageWrapper() {
function PageWrapper(props) {
return (
<div>
<h1> We love our users! </h1>
<Outlet />
{props.children}
<A href="/">Back Home</A>

@@ -462,3 +344,3 @@ </div>

The routes are still configured the same, but now the route elements will appear inside the parent element where the `<Outlet/>` was declared.
The routes are still configured the same, but now the route elements will appear inside the parent element where the `props.children` was declared.

@@ -470,5 +352,5 @@ You can nest indefinitely - just remember that only leaf nodes will become their own routes. In this example, the only route created is `/layer1/layer2`, and it appears as three nested divs.

path="/"
element={
component={(props) =>
<div>
Onion starts here <Outlet />
Onion starts here {props.children}
</div>

@@ -479,9 +361,10 @@ }

path="layer1"
element={
component={(props) =>
<div>
Another layer <Outlet />
Another layer {props.children}
</div>
}
>
<Route path="layer2" element={<div>Innermost layer</div>}></Route>
<Route path="layer2"
component={() => <div>Innermost layer</div>}> </Route>
</Route>

@@ -491,31 +374,146 @@ </Route>

If you declare a `data` function on a parent and a child, the result of the parent's data function will be passed to the child's data function as the `data` property of the argument, as described in the last section. This works even if it isn't a direct child, because by default every route forwards its parent's data.
## Data APIs
## Hash Mode Router
### `cache`
By default, Solid Router uses `location.pathname` as route path. You can simply switch to hash mode through the `source` property on `<Router>` component.
To prevent duplicate fetching and to trigger handle refetching we provide a cache api. That takes a function and returns the same function.
```jsx
import { Router, hashIntegration } from "@solidjs/router";
const getUser = cache((id) => {
return (await fetch(`/api/users${id}`)).json()
}, "users") // used as cache key + serialized arguments
```
It is expected that the arguments to the cache function are serializable.
<Router source={hashIntegration()}>
<App />
</Router>;
This cache accomplishes the following:
1. It does just deduping on the server for the lifetime of the request.
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.
3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
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.
This cache can be defined anywhere and then used inside your components with:
### `createAsync`
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 ready causes Suspense/Transitions to trigger.
```jsx
const user = createAsync(() => getUser(params.id))
```
## Memory Mode Router
`createAsync` is designed to only work with cached functions otherwise it will over fetch. If not using `cache`, continue using `createResource` instead.
You can also use memory mode router for testing purpose.
### `action`
Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response builders can be found below(TODO).
```jsx
import { Router, memoryIntegration } from "@solidjs/router";
// anywhere
const myAction = action(async (data) => {
await doMutation(data);
return redirect("/", {
invalidate: [getUser, data.id]
}) // returns a response
});
<Router source={memoryIntegration()}>
<App />
</Router>;
// in component
<form action={myAction} />
//or
<button type="submit" formaction={myAction}></button>
```
#### Notes of `<form>` implementation and SSR
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.
```jsx
const myAction = action(async (args) => {}, "my-action");
```
### `useAction`
Instead of forms you can use actions directly by wrapping them in a `useAction` primitive. This is how we get the router context.
```jsx
// in component
const submit = useAction(myAction)
submit(...args)
```
The outside of a form context you can use custom data instead of formData, and these helpers preserve types.
### `useSubmission`/`useSubmissions`
Are used to injecting the optimistic updates while actions are in flight. They either return a single Submission(latest) or all that match with an optional filter function.
```jsx
type Submission<T, U> = {
input: T;
result: U;
error: any;
pending: boolean
clear: () => {}
retry: () => {}
}
const submissions = useSubmissions(action, (input) => filter(input));
const submission = useSubmission(action, (input) => filter(input));
```
### Load Functions
Even with the cache API 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.
To do this, we can call our cache function in the load function.
```js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
import { getUser } from ... // the cache function
const User = lazy(() => import("./pages/users/[id].js"));
// load function
function loadUser({params, location}) {
void getUser(params.id)
}
// Pass it in the route definition
<Route path="/users/:id" component={User} load={loadUser} />;
```
The load function is called when the Route is loaded or eagerly when links are hovered. Inside your page component you
```jsx
// pages/users/[id].js
import { getUser } from ... // the cache function
export default function User(props) {
const user = createAsync(() => getUser(props.params.id));
return <h1>{user().name}</h1>;
}
```
As its only argument, the load function is passed an object that you can use to access route information:
| key | type | description |
| -------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| params | object | The route parameters (same value as calling `useParams()` inside the route component) |
| location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
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.
```js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
import loadUser from "./pages/users/[id].data.js";
const User = lazy(() => import("/pages/users/[id].js"));
// In the Route definition
<Route path="/users/:id" component={User} load={loadUser} />;
```
## Config Based Routing
You don't have to use JSX to set up your routes; you can pass an object directly with `useRoutes`:
You don't have to use JSX to set up your routes; you can pass an object:

@@ -525,3 +523,3 @@ ```jsx

import { render } from "solid-js/web";
import { Router, useRoutes, A } from "@solidjs/router";
import { Router } from "@solidjs/router";

@@ -561,24 +559,4 @@ const routes = [

function App() {
const Routes = useRoutes(routes);
return (
<>
<h1>Awesome Site</h1>
<A class="nav" href="/">
Home
</A>
<A class="nav" href="/users">
Users
</A>
<Routes />
</>
);
}
render(
() => (
<Router>
<App />
</Router>
),
render(() =>
<Router>{routes}</Router>,
document.getElementById("app")

@@ -588,2 +566,65 @@ );

## Alternative Routers
### Hash Mode Router
By default, Solid Router uses `location.pathname` as route path. You can simply switch to hash mode through the `source` property on `<Router>` component.
```jsx
import { Router, hashIntegration } from "@solidjs/router";
<Router source={hashIntegration()} />;
```
### Memory Mode Router
You can also use memory mode router for testing purpose.
```jsx
import { Router, memoryIntegration } from "@solidjs/router";
<Router source={memoryIntegration()} />;
```
## Components
### `<A>`
Like the `<a>` tag but supports relative paths and active class styling.
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.
| prop | type | description |
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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. |
| noScroll | boolean | If true, turn off the default behavior of scrolling to the top of the new page |
| replace | boolean | If true, don't add a new entry to the browser history. (By default, the new page will be added to the browser history, so pressing the back button will take you to the previous route.) |
| state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating |
| inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) |
| activeClass | string | The class to show when the link is active |
| 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` |
### `<Navigate />`
Solid Router provides a `Navigate` component that works similarly to `A`, but it will _immediately_ navigate to the provided path as soon as the component is rendered. It also uses the `href` prop, but you have the additional option of passing a function to `href` that returns a path to navigate to:
```jsx
function getPath({ navigate, location }) {
// navigate is the result of calling useNavigate(); location is the result of calling useLocation().
// You can use those to dynamically determine a path to navigate to
return "/some-path";
}
// Navigating to /redirect will redirect you to the result of getPath
<Route path="/redirect" component={() => <Navigate href={getPath} />} />;
```
### `<Route>`
The Component for defining Routes:
| prop | type | description |
|-|-|-|
|TODO
## Router Primitives

@@ -670,14 +711,2 @@

### useRouteData
Retrieves the return value from the data function.
> In previous versions you could use numbers to access parent data. This is no longer supported. Instead the data functions themselves receive the parent data that you can expose through the specific nested routes data.
```js
const user = useRouteData();
return <h1>{user().name}</h1>;
```
### useMatch

@@ -693,6 +722,2 @@

### useRoutes
Used to define routes via a config object instead of JSX. See [Config Based Routing](#config-based-routing).
### useBeforeLeave

@@ -726,2 +751,20 @@

## Migrations from 0.8.x
v0.9.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application.
The biggest changes are around removed APIs that need to be replaced.
### `<Outlet>`, `<Routes>`, `useRoutes`
This is no longer used and instead will use `props.children` passed from into the page components for outlets. Nested Routes inherently cause waterfalls and are Outlets in a sense themselves. We do not want to encourage the pattern and if you must do it you can always nest `<Routers>` with appropriate base path.
## `element` prop removed from `Route`
Related without Outlet component it has to be passed in manually. At which point the `element` prop has less value. Removing the second way to define route components to reduce confusion and edge cases.
### `data` functions & `useRouteData`
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.
## SPAs in Deployed Environments

@@ -728,0 +771,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc