# Routing
Routers provide a reactive path and query parameters and allow navigating in their current context.

You can either implement your own router using the `Router` interface or use one of the provided implementations:

+ `HistoryRouter` - Uses the location and history API for navigation.
+ `HashRouter` - Uses the location hash.
+ `MemoryRouter` - Keeps state in memory instead of the browser location. This can be useful for testing purposes.

=== "JSX"
	```jsx
	import { Provide } from "rvx";
	import { ROUTER, HistoryRouter } from "rvx/router";

	<Provide context={ROUTER} value={new HistoryRouter()}>
		{() => <>
			Everything in here has access to the history router.
		</>}
	</Provide>
	```

=== "No Build"
	```jsx
	import { ROUTER, HistoryRouter } from "./rvx.router.js";

	ROUTER.provide(new HistoryRouter(), () => [
		"Everything in here has access to the history router."
	])
	```

The `Routes` component can be used to render content based on the current path.

=== "JSX"
	```jsx
	import { Provide } from "rvx";
	import { ROUTER, HistoryRouter, Routes } from "rvx/router";

	<Provide context={ROUTER} value={new HistoryRouter()}>
		{() => <>
			<Routes routes={[
				{ match: "/", content: () => "Home" },
				{ match: "/foo", content: ExamplePage },
				{ content: () => "Not found" },
			]} />
		</>}
	</Provide>

	function ExamplePage() {
		return <>Example</>;
	}
	```

=== "No Build"
	```jsx
	import { ROUTER, HistoryRouter, routes } from "./rvx.router.js";

	ROUTER.provide(new HistoryRouter(), () => [
		routes([
			{ match: "/", content: () => "Home" },
			{ match: "/foo", content: ExamplePage },
			{ content: () => "Not found" },
		]),
	])

	function ExamplePage() {
		return "Example";
	}
	```

## Route Matching
Routes are matched against the [normalized](#path-normalization) path in the order in which they are specified.

Strings match exactly that path and all sub paths if they end with a slash:

```jsx
[
	// Matches only "/foo":
	{ match: "/foo", ... },
	// Matches "/foo", "/foo/bar" etc.
	{ match: "/foo/", ... },
]
```

Regular expressions are tested against the [normalized](#path-normalization) path:
```jsx
[
	// Matches only "/foo":
	{ match: /^\/foo$/, ... },

	// Matches "/user/123":
	{
		match: /^\/user\/(\d+)$/,
		content: ({ params }) => {
			// The match is passed via the "params" property:
			return <>User id: {params[1]}</>;
		},
	},
]
```

Rvx itself doesn't provide any custom syntax for dynamic routes, but you can use a package like [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) if you need to:
```jsx
import { pathToRegexp } from "path-to-regexp";

[
	{ match: pathToRegexp("/user/:id"), ... }
]
```

Functions can return an object with the normalized matched path and optional parameters to indicate a match:

=== "JSX"
	```jsx
	import { normalize } from "rvx/router";

	[
		{
			match: path => {
				if (/\/foo(\/|$)/.test(path)) {
					return {
						path: normalize(path.slice(4)),
						params: 42,
					};
				}
			},
			content: ({ params }) => {
				return <>{params}</>;
			},
		}
	]
	```

=== "No Build"
	```jsx
	import { normalize } from "./rvx.router.js";

	[
		{
			match: path => {
				if (/\/foo(\/|$)/.test(path)) {
					return {
						path: normalize(path.slice(4)),
						params: 42,
					};
				}
			},
			content: ({ params }) => {
				return <>{params}</>;
			},
		}
	]
	```

## Redirects
Redirects can be built by immediately calling `push` or `replace` on a router:

=== "JSX"
	```jsx
	import { ROUTER } from "rvx/router";

	{
		// Redirect ".../foo" to ".../foo/bar":
		match: "/foo",
		content: () => ROUTER.current!.replace("/bar"),
	}

	{
		// Redirect ".../foo" to ".../bar":
		match: "/foo",
		content: () => ROUTER.current!.parent!.replace("/bar"),
	}

	{
		// Redirect ".../foo" to "/bar":
		match: "/foo",
		content: () => ROUTER.current!.root.replace("/bar"),
	}
	```

=== "No Build"
	```jsx
	import { ROUTER } from "./rvx.router.js";

	{
		// Redirect ".../foo" to ".../foo/bar":
		match: "/foo",
		content: () => ROUTER.current.replace("/bar"),
	}

	{
		// Redirect ".../foo" to ".../bar":
		match: "/foo",
		content: () => ROUTER.current.parent.replace("/bar"),
	}

	{
		// Redirect ".../foo" to "/bar":
		match: "/foo",
		content: () => ROUTER.current.root.replace("/bar"),
	}
	```

## Path Normalization
Paths are normalized, so that non-empty paths always start with a slash and the root path is represented as an empty string.

Below are some examples:

=== "JSX"
	```jsx
	import { normalize } from "rvx/router";

	normalize("") // ""
	normalize("/") // ""
	normalize("foo") // "/foo"
	normalize("/foo") // "/foo"
	normalize("/foo/") // "/foo/"

	// Trailing slashes can be discarded:
	normalize("/foo/", false) // "/foo"
	```

=== "No Build"
	```jsx
	import { normalize } from "./rvx.router.js";

	normalize("") // ""
	normalize("/") // ""
	normalize("foo") // "/foo"
	normalize("/foo") // "/foo"
	normalize("/foo/") // "/foo/"

	// Trailing slashes can be discarded:
	normalize("/foo/", false) // "/foo"
	```

## Navigation
The router in the current context can be used for navigation.

Routers implement a **push** function for regular navigation and a **replace** function for replacing the current path if possible.

=== "JSX"
	```jsx
	import { ROUTER } from "rvx/router";

	function ExamplePage() {
		const router = ROUTER.current!.root;
		return <button on:click={() => {
			router.push("/some-path");
		}}>Navigate</button>;
	}
	```

=== "No Build"
	```jsx
	import { e } from "./rvx.js";
	import { ROUTER } from "./rvx.router.js";

	function ExamplePage() {
		const router = ROUTER.current.root;
		return e("button").on("click", () => {
			router.push("/some-path");
		}).append("Navigate");
	}
	```

Note that the router instance is replaced with a [child router](#nested-routing) inside of routed content. In this case, the **root** property always provides access to the history router from above.

## Nested Routing
Routes can be arbitrarily nested with content in between.

The example below renders text for the paths `/, /foo/bar, /foo/baz`:

=== "JSX"
	```jsx
	import { Provide } from "rvx";
	import { ROUTER, HistoryRouter, Routes } from "rvx/router";

	<Provide context={ROUTER} value={new HistoryRouter()}>
		{() => <>
			<Routes routes={[
				{ match: "/", content: () => "Home" },
				{ match: "/foo/", content: () => {

					// This is a ChildRouter:
					ROUTER.current;

					return <Routes routes={[
						{ match: "/bar", content: () => "Bar" },
						{ match: "/baz", content: () => "Baz" },
					]} />;
				} },
			]} />
		</>}
	</Provide>
	```

=== "No Build"
	```jsx
	import { ROUTER, HistoryRouter, routes } from "./rvx.router.js";

	ROUTER.provide(new HistoryRouter(), () => [
		routes([
			{ match: "/", content: () => "Home" },
			{ match: "/foo/", content: () => {

				// This is a ChildRouter:
				ROUTER.current;

				return routes([
					{ match: "/bar", content: () => "Bar" },
					{ match: "/baz", content: () => "Baz" },
				]);
			} },
		]),
	])
	```

The router instance is replaced with a [child router](#nested-routing) inside of routed content which only exposes the unmatched rest path and navigates within the matched path. In the example above, the **innerRouter** navigates within **/foo**:
```jsx
// Navigates to /foo/bar:
innerRouter.push("/bar");

// To navigate globally, use the root router instead:
innerRouter.root.push("/foo/bar");
```

## Query Parameters
Query parameters can be reactively accessed via router instances.

=== "JSX"
	```jsx
	// Access the raw query string (without the leading "?"):
	ROUTER.current!.query?.raw;

	// Access the query as a URLSearchParams object:
	ROUTER.current!.query?.params;
	```

=== "No Build"
	```jsx
	// Access the raw query string (without the leading "?"):
	ROUTER.current.query?.raw;

	// Access the query as a URLSearchParams object:
	ROUTER.current.query?.params;
	```

Note that the `query` property is undefined if the current URL doesn't contain a query.

## Async Content
You can use the [`<Async>`](./async-utilities/async.md) component to dynamically import & render components.

=== "JSX"
	```jsx
	// example-page.tsx:
	export default function() {
		return <>Hello World!</>;
	}

	// main.tsx:
	import { Routes } from "rvx/router";
	import { Async } from "rvx/async";

	<Routes routes={[
		{
			match: "/",
			content: () => <Async source={() => import("./example-page")}>
				{pageModule => <pageModule.default />}
			</Async>
		},
	]} />
	```

=== "No Build"
	```jsx
	// example-page.js:
	export default function() {
		return "Hello World!";
	}

	// main.js:
	import { routes, Async } from "./rvx.router.js";

	routes([
		{
			match: "/",
			content: () => Async({
				source: () => import("./example-page"),
				children: pageModule => pageModule.default(),
			})
		},
	])
	```

Depending on how you want to implement things like error handling and loading indicators, you can build a small function like this:

=== "JSX"
	```jsx
	function page(importModule: () => Promise<{ default: () => unknown }>) {
		return () => <Async source={importModule}
			pending={() => <>Loading...</>}
			rejected={error => <>Error: {error}</>}
		>
			{pageModule => <pageModule.default />}
		</Async>;
	}

	<Routes routes={[
		{ match: "/", content: page(() => import("./home")) },
		{ match: "/example", content: page(() => import("./example")) },
	]} />
	```

=== "No Build"
	```jsx
	/**
	 * @param {() => Promise<{ default: () => unknown }>} importModule
	 */
	function page(importModule) {
		return () => Async({
			source: importModule,
			pending: () => "Loading...",
			rejected: error => ["Error: ", error],
			children: pageModule => pageModule.default(),
		});
	}

	routes([
		{ match: "/", content: page(() => import("./home")) },
		{ match: "/example", content: page(() => import("./example")) },
	])
	```
