Browser history
Browser history demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.
App.tsx
import './App.css';
import '@expressive/react';
import { BrowserRouter, Link, Route } from '@expressive/router';
import type { ReactNode } from 'react';
export default () => (
<div className="container">
<h1>Browser history</h1>
<p>
<code>BrowserRouter</code> binds the router to the address bar and browser
history. A nested <code>none</code> Route catches misses inside its section;
the root one catches the rest.
</p>
<BrowserRouter>
<Route as={Frame}>
<Route as={Home} />
<Route to="projects" as={Projects}>
<Route as={ProjectIndex} />
<Route to=":id" as={Project} />
<Route none as={ProjectNotFound} />
</Route>
<Route none as={NotFound} />
</Route>
</BrowserRouter>
<small>
Browser Back and Forward walk these pages - the URL is the source of
truth. The projects layout persists across every page
below <code>/projects</code>, its own not-found page included.
</small>
</div>
);
const Frame = (props: { children?: ReactNode }) => {
const { url } = BrowserRouter.get();
return (
<section className="browser">
<code className="address">{url}</code>
<nav className="nav">
<Link to="/">Home</Link>
<Link to="/projects">Projects</Link>
<Link to="/projects/ada">Ada</Link>
<Link to="/projects/ada/files">Section miss</Link>
<Link to="/elsewhere">App miss</Link>
</nav>
<div className="view">{props.children}</div>
</section>
);
};
const Home = () => <p>Choose a destination.</p>;
const Projects = (props: { children?: ReactNode }) => (
<section className="project">
<b>Projects</b>
{props.children}
</section>
);
const ProjectIndex = () => <p>Select a project.</p>;
const Project = () => {
const { match } = Route.get(true);
return <p>Project: {match!.id}</p>;
};
const ProjectNotFound = () => <p>No project page matches this URL.</p>;
const NotFound = () => <p>No application page matches this URL.</p>;
App.css
.browser {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--s3);
width: 100%;
}
.address {
width: 100%;
padding: var(--s2) var(--s3);
color: var(--accent);
background: var(--surface-2);
border-radius: var(--r-sm);
}
.nav {
display: flex;
flex-wrap: wrap;
gap: var(--s2) var(--s3);
}
.nav a {
color: var(--accent);
text-decoration: none;
}
.nav a:hover {
text-decoration: underline;
}
.view {
width: 100%;
min-height: 6rem;
padding: var(--s4);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r);
}
.view p {
margin: var(--s2) 0 0;
}
.project {
display: flex;
flex-direction: column;
gap: var(--s2);
}