React Navigator Web
Simple easy to use router for react.
Main features:
- Simple api
- Focus on nested routing
- Support for animating transitions
- Switch router (typical router, no transition and state is typically reset)
- Stack router (stack of routes, transitions supported and state is typically kept)
- Server side rendering supported
Note: the documentation is incomplete, the project isn't well tested and it's not ready for production.
Install
npm install react-navigator-web
yarn add react-navigator-web
pnpm add react-navigator-web
Usage
import React, { useState } from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import { ReactNavigator, useLocation, ReactNavigatorOptions, PageRoute, useRoute, useQueryState, useNavigator, useRenderProps } from 'react-navigator'
import { Link } from 'react-navigator/dist/components'
function AnyPage() {
const { id } = useLocation().params
return <Scaffold>id: {id as string}</Scaffold>
}
function Home() {
return <Scaffold>
<AppBar />
<main>
<h1>Home</h1>
<p>
<navigator.Link href="/about">About</navigator.Link>
</p>
<p>
<Link navigator={navigator} href="/dashboard/settings">Dashboard (settings)</Link>
</p>
<p>
<Link href="/123">Post 123</Link>
</p>
</main>
<Footer />
</Scaffold>
}
function Dashboard() {
const location = useLocation()
const route = useRoute()
const [num] = useState(() => Math.floor(Math.random() * 100))
const [something, setSomething] = useQueryState('something', {
mode: 'push',
parse: (value) => value === '1',
stringify: (value) => value ? '1' : null,
})
function toggleShowAll() {
setSomething(!something)
}
const [error, setError] = useState<Error | undefined>(undefined)
if (error) {
throw error
}
return <Scaffold>
<AppBar />
<div>
<p>Dashboard. Sub path: {location.childPath}. Random: {num}</p>
<button type='button' onClick={toggleShowAll}>Toggle something</button>
<p>Something: {something ? 'yes' : 'no'}</p>
<button onClick={() => setError(new Error('Demo error'))}>Throw error</button>
</div>
<Footer />
</Scaffold>
}
const options: ReactNavigatorOptions = {
defaultRouteBackground: '#f1f1f1',
defaultRouteTransitionDuration: 300,
darkMode: false,
defaultRouteAnimation: 'slide'
}
const navigator = new ReactNavigator({
'/': () => <Home />,
'/:id': () => <AnyPage />,
'/about': () => <Scaffold><AppBar />About page<Footer /></Scaffold>,
'help': () => <Scaffold>Help page</Scaffold>,
'help/:topic': () => <Scaffold>About page</Scaffold>,
'dashboard/*': () => <Dashboard />,
}, options);
function AppBar() {
return <header style={{ width: '100%', padding: '5px' }}>
<div style={{ width: '100%', padding: '5px', backgroundColor: '#ccc', display: 'flex', justifyContent: 'space-between' }}>
<div>
<button onClick={() => navigator.back()}>Back</button>
<button onClick={() => navigator.forward()}>Forward</button>
<button onClick={() => navigator.pop({ skipRouteCheck: true })}>Close</button>
</div>
<button onClick={() => showMenu()}>Menu</button>
</div>
</header>
}
function showMenu() {
navigator.push(new PageRoute({
opaque: false,
transitionDuration: 100,
animation: 'fade',
popOnBack: true,
removeOnPush: true,
builder(props) {
return <Menu />
},
}))
}
function Menu() {
return <Center>
<div style={{ backgroundColor: 'rgba(0, 0, 0, 0.2)', width: '100%', height: '100%' }} onClick={e => navigator.pop()}>
<Center>
<main style={{ maxWidth: '300px', padding: '20px', width: '100%' }}>
<ul style={{ display: 'block', padding: '20px', backgroundColor: '#F1F1F1', boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.2)', listStyle: 'none' }}
onClick={e => e.stopPropagation()}
>
<li><Link href='/'>Home</Link></li>
<li><Link href='/about'>About</Link></li>
<li><Link href='/dashboard'>Dashboard</Link></li>
<li><Link href='/dashboard/settings'>Settings</Link></li>
<li><Link href='/123'>123</Link></li>
<li><Link href='/567'>567</Link></li>
</ul>
</main>
</Center>
</div>
</Center>
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<div style={{ position: 'fixed', height: '100%', width: '100%' }}>
<navigator.Stack />
</div>
</React.StrictMode>,
);
function Scaffold(props: { children: React.ReactNode }) {
return <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-between', height: '100%', width: '100%', border: '2px dashed black' }}>
{props.children}
</div>
}
function Footer() {
return <footer style={{ width: '100%', padding: '5px' }}>
<div style={{ width: '100%', padding: '5px', backgroundColor: '#ccc', display: 'flex', justifyContent: 'left' }}>
<a href="https://github.com/Martoxdlol/react-navigator">GitHub</a>
</div>
</footer>
}
function Center(props: { children: React.ReactNode }) {
return <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', }}>
{props.children}
</div>
}