Skip to content

Commit

Permalink
Added usePrefetch
Browse files Browse the repository at this point in the history
  • Loading branch information
smikhalevski committed Nov 22, 2024
1 parent 4c29708 commit 618e07c
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 16 deletions.
1 change: 1 addition & 0 deletions src/main/Presenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class Presenter {
const payload = loadRoute(routeMatch.route, {
params: routeMatch.params,
context: this.context,
signal: new AbortController().signal,
isPreload: false,
});

Expand Down
84 changes: 69 additions & 15 deletions src/main/Router.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
import { PubSub } from 'parallel-universe';
import { AbortablePromise, PubSub } from 'parallel-universe';
import { ComponentType } from 'react';
import { loadRoute } from './__loadRoute';
import { matchRoutes, RouteMatch } from './__matchRoutes';
import { Route } from './__Route';
import { Fallbacks, RouterOptions, To } from './__types';
import { Fallbacks, Location, RouterOptions, To } from './__types';
import { noop, toLocation } from './__utils';
import { Presenter, reconcilePresenters } from './Presenter';
import { toLocation } from './__utils';

/**
* An event dispatched by a {@link Router} when a navigation occurs.
*
* @group Routing
*/
export interface NavigateEvent {
type: 'navigate';

/**
* A location to which a router has navigated.
*/
location: Location;
}

/**
* An event dispatched by a {@link Router} when a redirect was requested by a router.
*
* @group Routing
*/
export interface RedirectEvent {
type: 'redirect';

/**
* A location or a URL to which a redirect should be made.
*/
to: To | string;
}

/**
* An event dispatched by a {@link Router}.
*
* @group Routing
*/
export type RouterEvent = NavigateEvent | RedirectEvent;

/**
* A router that matches routes by a location.
Expand Down Expand Up @@ -33,7 +68,7 @@ export class Router<Context = any> implements Fallbacks {
loadingComponent: ComponentType | undefined;
notFoundComponent: ComponentType | undefined;

protected _pubSub = new PubSub();
protected _pubSub = new PubSub<RouterEvent>();

/**
* Creates a new instance of a {@link Router}.
Expand Down Expand Up @@ -62,26 +97,45 @@ export class Router<Context = any> implements Fallbacks {
}

navigate(to: To): void {
this.presenters = reconcilePresenters(this, this.presenters, this.match(to));
this._pubSub.publish();
const location = toLocation(to);

this.presenters = reconcilePresenters(
this,
this.presenters,
matchRoutes(location.pathname, location.searchParams, this.routes)
);

this._pubSub.publish({ type: 'navigate', location });
}

/**
* Prefetches components and data of routes matched by a location.
*
* @param to A location or a route to prefetch.
* @returns An promise that can be aborted is prefetch must be discarded.
*/
prefetch(to: To): void {
for (const routeMatch of this.match(to)) {
loadRoute(routeMatch.route, {
params: routeMatch.params,
context: this.context,
isPreload: true,
});
}
prefetch(to: To): AbortablePromise<void> {
return new AbortablePromise((resolve, _reject, signal) => {
const routePayloads = this.match(to).map(routeMatch =>
loadRoute(routeMatch.route, {
params: routeMatch.params,
context: this.context,
signal,
isPreload: true,
})
);

resolve(Promise.all(routePayloads).then(noop));
});
}

subscribe(listener: () => void): () => void {
/**
* Subscribes a listener to events dispatched by a router.
*
* @param listener A listener to subscribe.
* @returns A callback that unsubscribe a listener.
*/
subscribe(listener: (event: RouterEvent) => void): () => void {
return this._pubSub.subscribe(listener);
}
}
5 changes: 5 additions & 0 deletions src/main/__types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ export interface LoaderOptions<Params = any, Context = any> {
*/
context: Context;

/**
* A signal that is aborted if a loader result isn't needed anymore.
*/
signal: AbortSignal;

/**
* `true` if a loader is called during {@link Router.preload preload}.
*/
Expand Down
51 changes: 51 additions & 0 deletions src/main/__usePrefetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import isDeepEqual from 'fast-deep-equal';
import { useEffect, useRef } from 'react';
import { To } from './__types';
import { useRouter } from './__useRouter';

/**
* Prefetches components and data of routes matched by a location after a component has mounted.
*
* @param to A location or a route to prefetch.
* @see {@link Prefetch}
* @group Hooks
* @example
* const userRoute = createRoute('/userRoute/:userId');
*
* usePrefetch(userRoute.getLocation({ userId: 37 }));
*/
export function usePrefetch(to: To): void {
const toRef = useRef<To>();
const router = useRouter();

useEffect(() => {
if (isDeepEqual(toRef.current, to)) {
return;
}
toRef.current = to;
router.prefetch(to);
}, [router, to]);
}

/**
* Props of the {@link Prefetch} component.
*
* @group Components
*/
export interface PrefetchProps {
/**
* A location or a route to prefetch.
*/
to: To;
}

/**
* Prefetches components and data of routes matched by a location after a component has mounted.
*
* @see {@link usePrefetch}
* @group Components
*/
export function Prefetch(props: PrefetchProps): null {
usePrefetch(props.to);
return null;
}
2 changes: 2 additions & 0 deletions src/main/__utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export function toLocation(to: To): Location {

return { pathname, searchParams, hash, state };
}

export function noop() {}
2 changes: 1 addition & 1 deletion src/main/history/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { forwardRef, HTMLAttributes, MouseEventHandler, useEffect } from 'react';
import { useRouter } from '../RouterProvider';
import { To } from '../__types';
import { useRouter } from '../__useRouter';
import { toLocation } from '../__utils';
import { useHistory } from './useHistory';

Expand Down
3 changes: 3 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ export { Outlet } from './Outlet';
export { PathnameTemplate } from './__PathnameTemplate';
export { redirect, Redirect } from './__redirect';
export { Route } from './__Route';
export { useRouter } from './__useRouter';
export { usePrefetch, Prefetch } from './__usePrefetch';
export { Router } from './Router';
export { useRouteParams, useRouteData, useRouteError } from './hooks';

export type { PrefetchProps } from './__usePrefetch';
export type { LinkProps } from './history/Link';
export type { HistoryOptions, History, SearchParamsAdapter } from './history/types';

Expand Down

0 comments on commit 618e07c

Please sign in to comment.