The next generation router for your app
yarn add effector trace-router
Create a router:
import history from 'history/browser';
import { createRouter, history } from 'trace-router';
export const router = createRouter({ history });
Create routes:
// This route is used only for redirection below
export const exactRoot = router.add({ path: '/' });
// User section
export const user = router.add('/user(/.*)?'); // parent route
export const userProfile = router.add('/user');
export const userTickets = router.add('/user/tickets');
export const userTicket = router.add<{ id: number }>('/user/tickets/:id');
// Info section
export const joinUs = router.add('/join-us');
export const about = router.add('/about');
export const privacy = router.add('/privacy');
// Merge routes to create a parent route
// When you can't create common path
export const info = router.merge([joinUs, about, privacy]);
// Redirect from "/" to "/user"
exactRoot.visible.watch(visible => {
if (visible) {
user.redirect();
}
});
Use routes in React (trace-router-react
package):
export const Root = () => (
<>
{useRoute(user) && <UserPage />}
{useRoute(info) && <InfoPage />}
{useStore(router.noMatches) && <NotFound />}
</>
);
export const UserPage = () => (
<AppFrame>
<UserTemplate>
{useRoute(userProfile) && <UserProfile />}
{useRoute(userTickets) && <UserTickets />}
{useRoute(userTicket) && <UserTicket />}
</UserTemplate>
</AppFrame>
);
export const InfoPage = () => (
<AppFrame>
<InfoTemplate>
{useRoute(joinUs) && <JoinUs />}
{useRoute(about) && <About />}
{useRoute(privacy) && <Privacy />}
</InfoTemplate>
</AppFrame>
);
You can also use Route
component instead of a hook:
<Route of={map} component={MapPage} />
Use links to navigate routes directly:
<Link to={about}>About</Link>
Use can add params to the route (if it has ones):
<Link to={userTicket} params={{ id: 100 }}>
Month
</Link>
The above link compiles to something like:
<a href="/user-tiket/100" onClick={/* prevent default & navigate */}>
Join Us
</a>
Here is how you compile route to a string
:
const href = route.compile({
params: { id: 100 },
query: {
lang: 'ru',
},
hash: '#description',
});
Manual route navigation:
<Button onClick={() => product.navigate({ id: '100' })} />
or redirect
+ compile
as an example:
<Button
onClick={() =>
product.router.redirect({
to: product.compile({ params: { id: '100' } }),
state: { back },
})
}
/>
You can use another history for a router:
import hashHistory from 'history/hash';
import { router } from '~/core/router';
router.use(hashHistory);
You can bind one router to another:
export const product = router
.add<{ tab: string }>('/product:tab(.*)?')
.bind('tab', { router: tabRouter });
.bind
method binds child router path to a parent router parameter
You can have an url /product/info
where:
/product
- the path of the main router (without a parameter)
/info
- tabRouter path
Router
export type Router<Q extends Query = Query, S extends State = State> = {
history: History<S>;
historyUpdated: Event<Update<S>>;
historyUpdate: Store<Update<S>>;
navigate: Event<ToLocation<S>>;
redirect: Event<ToLocation<S>>;
shift: Event<Delta>;
back: Event<void>;
forward: Event<void>;
location: Store<Location<S>>;
action: Store<Action>;
pathname: Store<Pathname>;
search: Store<Search>;
hash: Store<Hash>;
state: Store<S>;
key: Store<Key>;
href: Store<Href>;
query: Store<Q>;
hasMatches: Store<boolean>;
noMatches: Store<boolean>;
add: <P extends Params = Params>(
pathConfig: Pattern | RouteConfig
) => Route<P, Router<Q, S>>;
merge: <T extends Route[]>(routes: T) => MergedRoute;
none: <T extends Route[]>(routes: T) => MergedRoute;
use: (
givenHistory: BrowserHistory<S> | HashHistory<S> | MemoryHistory<S>
) => void;
};
Route
export type Route<P extends Params = Params, R = Router> = {
visible: Store<boolean>;
params: Store<null | P>;
config: RouteConfig;
compile: (compileConfig?: CompileConfig<P>) => string;
router: R extends Router<infer Q, infer S> ? Router<Q, S> : never;
navigate: Event<P | void>;
redirect: Event<P | void>;
bindings: Partial<{ [K in keyof P]: BindConfig }>;
bind: (
param: keyof P,
bindConfig: {
router: Router;
parse?: (rawParam?: string) => string | undefined;
format?: (path?: string) => string | undefined;
}
) => Route<P, R>;
};
Other typings
export type ToLocation<S extends State = State> =
| string
| { to?: To; state?: S };
export type Delta = number;
export type Href = string;
export type Pattern = string;
export interface Query extends ObjectString {}
export interface Params extends ObjectUnknown {}
export type RouterConfig<S extends State = State> = {
history?: BrowserHistory<S> | HashHistory<S> | MemoryHistory<S>;
root?: InitialEntry;
};
export type RouteConfig = {
path: Pattern;
matchOptions?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions;
};
export type CompileConfig<P extends Params = Params> = {
params?: P;
query?: string[][] | Record<string, string> | string | URLSearchParams;
hash?: string;
options?: ParseOptions & TokensToFunctionOptions;
};
export type BindConfig = {
router: Router;
parse?: (rawParam?: string) => string | undefined;
format?: (path?: string) => string | undefined;
};
export type MergedRoute = {
visible: Store<boolean>;
routes: Route[];
configs: RouteConfig[];
};
Give trace-router
a star!
GitHub ★: https://github.com/doasync/trace-router