UNPKG

mobx-react-routing

Version:
155 lines (154 loc) 5.05 kB
import { LinkedAbortController } from 'linked-abort-controller'; import { action, computed, makeObservable, observable, reaction, runInAction, } from 'mobx'; import { createBrowserRouter, createHashRouter, createMemoryRouter, matchPath, } from 'react-router-dom'; import { generateId } from 'yummies/id'; import { createRoute } from '../create-route.js'; import { QueryParamsBase } from '../query-params/index.js'; export class RouterStoreBase { abortController; queryParams; matches; location; blockers = new Set(); router; fallbackComponent; errorBoundaryComponent; get lastMatch() { return this.matches.at(-1) ?? null; } createRoute(declaration, index, parentPath) { return createRoute({ declaration, router: this, index, parentPath, factory: (config, declaration) => { const VM = config.VM; return new VM(declaration, this, config); }, }); } constructor({ routes, fallbackComponent, errorBoundaryComponent, abortSignal, type = 'browser', ...libRouterParams }) { this.abortController = new LinkedAbortController(abortSignal); this.fallbackComponent = fallbackComponent; this.errorBoundaryComponent = errorBoundaryComponent; let createRouterFn; switch (type) { case 'browser': { createRouterFn = createBrowserRouter; break; } case 'hash': { createRouterFn = createHashRouter; break; } case 'memory': { createRouterFn = createMemoryRouter; break; } } this.router = createRouterFn(routes.map((route, index) => this.createRoute(route, index, [])), libRouterParams); this.queryParams = new QueryParamsBase(this.router); this.matches = this.collectRouteMatches(this.router.state.matches); this.location = { ...this.router.state.location }; observable(this, 'matches'); observable(this, 'location'); observable(this, 'blockers'); computed(this, 'blocked'); computed(this, 'lastMatch'); action.bound(this, 'navigate'); action.bound(this, 'blockRouting'); action.bound(this, 'unblockRouting'); action.bound(this, 'back'); makeObservable(this); const subscription = this.router.subscribe((state) => { runInAction(() => { this.matches = this.collectRouteMatches(state.matches); this.location = { ...state.location }; }); }); this.abortController.signal.addEventListener('abort', () => { subscription(); }); } collectRouteMatches(matches) { return matches.map((match) => { return { id: match.route.id, params: match.params, path: match.route.path, pathname: match.pathname, }; }); } createPath(to) { if (typeof to === 'string') { const [pathname, search] = to.split('?', 2); return { pathname, search: search ? `?${search}` : '', hash: '', }; } else { return { pathname: to.pathname, search: this.queryParams.buildSearchString(to.search || {}), hash: '', }; } } get currentUrl() { return (this.location.pathname + (this.location.search ? `?${this.location.search}` : '')); } createUrl(to) { const path = this.createPath(to); return `${path.pathname}${path.search}${path.hash}`; } async navigate(to, options) { await this.router.navigate(this.createUrl(to), options); } get blocked() { return this.blockers.size > 0; } blockRoutingIf(expression, id = generateId()) { const disposeFunction = reaction(expression, (blocks) => { if (blocks) { this.blockRouting(id); } else { this.unblockRouting(id); } }, { fireImmediately: true }); return () => { this.unblockRouting(id); disposeFunction(); }; } blockRouting(id = generateId()) { if (!this.blockers.has(id)) { this.blockers.add(id); } return id; } unblockRouting(id) { this.blockers.delete(id); } getInstance() { return this.router; } async back() { await this.router.navigate(-1); } findMatch = (pathOrPattern) => { return matchPath(pathOrPattern, this.location.pathname); }; isMatched = (pathOrPattern) => { return !!this.findMatch(pathOrPattern); }; clean() { this.blockers.clear(); this.abortController.abort(); } }