Skip to content

Commit

Permalink
feat: Simplify the useRouter hook to just one use format
Browse files Browse the repository at this point in the history
  • Loading branch information
MatiasWorker committed May 21, 2024
1 parent c3b89cf commit 92b4a6b
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 188 deletions.
3 changes: 1 addition & 2 deletions packages/use-router/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@atomico/use-router",
"description": "Series of utilities in hooks format to extend the operation of Atomico",
"version": "1.0.1",
"version": "1.1.0",
"type": "module",
"license": "MIT",
"author": {
Expand Down Expand Up @@ -59,4 +59,3 @@
}
}
}

16 changes: 3 additions & 13 deletions packages/use-router/src/history.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import { addListener } from "@atomico/use-listener";
/**
* @param {string} url
* @param {string} [title]
*/
export function redirect(path, title = path) {
export function redirect(path: string, title = path): void {
if (history.state?.path === path) return;
history.pushState({ path }, title, path);
window.dispatchEvent(new PopStateEvent("popstate"));
globalThis.dispatchEvent(new PopStateEvent("popstate"));
}
/**
*
* @param {(ev: PopStateEvent) => void} handler
*/
export const listener = (handler) => addListener(window, "popstate", handler);

export const getPath = () =>
export const getPath = (): string =>
location.pathname + location.hash + location.search;
152 changes: 35 additions & 117 deletions packages/use-router/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,122 +1,40 @@
import { Ref, useState, useEffect } from "atomico";
import { getPath, listener, redirect } from "./history.js";
import { Routes, matches, getMatch, RouterCallback } from "./matches.js";
export { redirect, getPath } from "./history.js";
import { useMemo, useState, createRef } from "atomico";
import { createMatch, Params } from "@uppercod/exp-route";
import { useListener } from "@atomico/use-listener";
import { useCurrentValue } from "@atomico/use-current-value";
import { Match, Params } from "@uppercod/exp-route";

const DefaultState = {};

export function useRouter<T>(routes: Routes): [T, string, Params, Params] {
const [state, setState] = useState<{
path?: string;
result?: any;
}>(DefaultState);
const refRoutes = useCurrentValue(routes);

useEffect(() => {
// Returns to the default state to recycle the routes object
setState(DefaultState);

const reduce = () => {
setState((current) => {
const path = getPath();
return current.path != path
? {
path,
result: matches(refRoutes.current, path),
}
: current;
});
};

reduce();

return listener(reduce);
}, Object.keys(routes));

return state.result || [];
}

export function useRoute<T>(
path: string,
callback: RouterCallback = (param) => param,
): [T, string, Params, Params] {
const routes = { [path]: callback };
return useRouter(routes);
}
/**
* Create a match function to manually compare route matches,
* the instance of this hook listens for route changes
* @example
* ```js
* const match = useRouteMatch();
*
* if(match("/")){
* console.log("in root")
* }
*
* console.log(match("/:id"))
* ```
*/
export function useRouteMatch(): (path: string) => Match {
const [state, setState] = useState(getPath);
useEffect(() => listener(() => setState(getPath)), []);
// @ts-ignore
return (path) => getMatch(path)(state);
import { getPath, redirect } from "./history.js";
export * from "./history.js";

interface RouteSwitch<Result> {
[path: string]: (
params: Params,
data: { id: string; path: string },
) => Result;
}

/**
* Capture the click events of a reference to find
* if a node declares href to associate redirection
*/
export function useRedirect(
ref: Ref<Element>,
{
proxy,
composed = true,
}: { proxy?: (path: string) => string; composed?: boolean } = {},
) {
useListener(
ref,
"click",
(event) => {
const { current } = ref;
const { shadowRoot } = current;

const path = event.composedPath();

const index = path.indexOf(current);

const insetShadowRoot = path
.slice(0, index)
.find((el) => el instanceof ShadowRoot);

if (!composed && insetShadowRoot !== shadowRoot) return;

let target;

while ((target = path.shift())) {
if (
target.hasAttribute &&
target.hasAttribute("href") &&
!target.hasAttribute("ignore")
) {
const href = target.getAttribute("href");
if (
!target.hasAttribute("target") &&
!/^(http(s){0,1}:){0,1}\/\//.test(href)
) {
event.preventDefault();
redirect(proxy ? proxy(href) : href);
}
break;
}
const refGlobalThis = createRef(globalThis);

export function useRouter<Result = any>(
router: RouteSwitch<Result>,
): {
id: string;
path: string;
params: Params;
result: Result;
redirect: (path: string, title?: string) => void;
} {
const [id, setId] = useState(getPath);

useListener(refGlobalThis, "popstate", () => {
setId(getPath);
});

return useMemo(() => {
for (const path in router) {
const params = createMatch(path)(id);
if (params) {
const result = router[path](params, { id, path });
return { result, id, path, params, redirect };
}
},
{ capture: true },
);
}
}, [id]);
}
30 changes: 0 additions & 30 deletions packages/use-router/src/matches.ts

This file was deleted.

42 changes: 21 additions & 21 deletions packages/use-router/test/use-router.test.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import { it, expect } from "vitest";
import { createHooks } from "atomico/test-hooks";
import { useRoute, useRouteMatch } from "../src/";
import { expect, it } from "vitest";
import { redirect, useRouter } from "../src/";

it("useRouter", async () => {
const hooks = createHooks(
() => {
hooks.load(load);
},
{ updated: Promise.resolve() },
);

const router = [];

it("useRouter", () => {
const hooks = createHooks(() => {});
let render = 0;
function load() {
const result = useRoute("/[...any]");
const route = useRouter({
"/": () => "home",
"/config": () => "config",
});

if (render++) {
expect(result[0]).to.deep.equal({ any: "" });
} else {
expect(result).to.deep.equal([]);
}
router.push(route.result);
}

hooks.load(load);

hooks.cleanEffects()()();

hooks.load(load);
});

it("useRouteMatch", () => {
const hooks = createHooks();
await new Promise((resolve) => setTimeout(resolve, 100));

function load() {
const match = useRouteMatch();
redirect("/config");

expect(match("/[id]")).to.deep.equal({ id: "" });
}
await new Promise((resolve) => setTimeout(resolve, 100));

hooks.load(load);
expect(router).to.deep.equal(["home", "config"]);
});
6 changes: 1 addition & 5 deletions packages/use-router/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ export default defineConfig({
target: "esnext",
},
test: {
browser: {
enabled: true,
headless: true,
name: "chrome", // browser name is required
},
environment: "jsdom",
},
plugins: [
...atomico({
Expand Down

0 comments on commit 92b4a6b

Please sign in to comment.