Skip to content

Commit

Permalink
useHashLocation must trim query string, useSearch must parse query st…
Browse files Browse the repository at this point in the history
…ring from hash location
  • Loading branch information
GravityTwoG authored and molefrog committed Jul 4, 2024
1 parent e36651d commit 481f589
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 3 deletions.
33 changes: 32 additions & 1 deletion packages/wouter/src/use-hash-location.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,39 @@ const subscribeToHashUpdates = (callback) => {
};
};

export const useLocationProperty = (fn, ssrFn) =>
useSyncExternalStore(subscribeToHashUpdates, fn, ssrFn);

// when useHashLocation is used, location.search is always empty
// so we must retrieve string from the hash
const currentSearch = () => {
const hash = location.hash;
const hashLocation = "/" + hash.replace(/^#?\/?/, "");

const questionMarkIdx = hashLocation.indexOf("?");
if (questionMarkIdx !== -1) {
return hashLocation.slice(questionMarkIdx + 1, hashLocation.length);
}

return "";
};

export const useSearch = ({ ssrSearch = "" } = {}) =>
useLocationProperty(currentSearch, () => ssrSearch);

// leading '#' is ignored, leading '/' is optional
const currentHashLocation = () => "/" + location.hash.replace(/^#?\/?/, "");
const currentHashLocation = () => {
const hash = location.hash;
const hashLocation = "/" + hash.replace(/^#?\/?/, "");

// remove query string
const questionMarkIdx = hashLocation.indexOf("?");
if (questionMarkIdx !== -1) {
return hashLocation.slice(0, questionMarkIdx);
}

return hashLocation;
};

export const navigate = (to, { state = null } = {}) => {
// calling `replaceState` allows us to set the history
Expand Down
23 changes: 22 additions & 1 deletion packages/wouter/test/use-hash-location.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { renderHook, render } from "@testing-library/react";
import { renderToStaticMarkup } from "react-dom/server";

import { Router, Route, useLocation, Link } from "wouter";
import { useHashLocation } from "wouter/use-hash-location";
import { useHashLocation, useSearch } from "wouter/use-hash-location";

import { waitForHashChangeEvent } from "./test-utils";
import { ReactNode, useSyncExternalStore } from "react";
Expand All @@ -29,6 +29,14 @@ it("isn't sensitive to leading slash", () => {
expect(path).toBe("/app/users");
});

it("isn't sensitive to query string", () => {
location.hash = "/app/users?foo=bar";
const { result } = renderHook(() => useHashLocation());
const [path] = result.current;

expect(path).toBe("/app/users");
});

it("rerenders when hash changes", async () => {
const { result } = renderHook(() => useHashLocation());

Expand Down Expand Up @@ -196,3 +204,16 @@ it("defines a custom way of rendering link hrefs", () => {

expect(getByTestId("link")).toHaveAttribute("href", "#/app");
});

it("useSearch returns correct query string when useHashLocation is used", () => {
location.hash = "/";
const { result } = renderHook(() => useHashLocation());
const [, navigate] = result.current;

navigate("/?hello=world");
const {
result: { current: search },
} = renderHook(() => useSearch());

expect(search).toBe("hello=world");
});
9 changes: 8 additions & 1 deletion packages/wouter/types/use-browser-location.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Path, SearchString } from "./location-hook.js";

type Primitive = string | number | bigint | boolean | null | undefined | symbol;
export type Primitive =
| string
| number
| bigint
| boolean
| null
| undefined
| symbol;
export const useLocationProperty: <S extends Primitive>(
fn: () => S,
ssrFn?: () => S
Expand Down
8 changes: 8 additions & 0 deletions packages/wouter/types/use-hash-location.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Path } from "./location-hook.js";
import { BrowserSearchHook, Primitive } from "./use-browser-location.js";

export function navigate<S = any>(to: Path, options?: { state: S }): void;

export const useLocationProperty: <S extends Primitive>(
fn: () => S,
ssrFn?: () => S
) => S;

export const useSearch: BrowserSearchHook;

export function useHashLocation(options?: {
ssrPath?: Path;
}): [Path, typeof navigate];

0 comments on commit 481f589

Please sign in to comment.