Skip to content

Commit 86c3019

Browse files
feat(memory-location): add initial location state support
1 parent 2061eac commit 86c3019

5 files changed

Lines changed: 103 additions & 15 deletions

File tree

packages/wouter-preact/types/memory-location.d.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ type Navigate<S = any> = (
55
options?: { replace?: boolean; state?: S; transition?: boolean }
66
) => void;
77

8-
type HookReturnValue = { hook: BaseLocationHook; navigate: Navigate };
8+
type HookReturnValue<S = unknown> = {
9+
hook: BaseLocationHook;
10+
navigate: Navigate<S>;
11+
readonly state: S | null;
12+
};
913
type StubHistory = { history: Path[]; reset: () => void };
1014

11-
export function memoryLocation(options?: {
15+
export function memoryLocation<S = unknown>(options?: {
1216
path?: Path;
17+
state?: S;
1318
static?: boolean;
1419
record?: false;
15-
}): HookReturnValue;
16-
export function memoryLocation(options?: {
20+
}): HookReturnValue<S>;
21+
export function memoryLocation<S = unknown>(options?: {
1722
path?: Path;
23+
state?: S;
1824
static?: boolean;
1925
record: true;
20-
}): HookReturnValue & StubHistory;
26+
}): HookReturnValue<S> & StubHistory;

packages/wouter/src/memory-location.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@ import { useSyncExternalStore } from "./react-deps.js";
88
export const memoryLocation = ({
99
path = "/",
1010
searchPath = "",
11+
state = null,
1112
static: staticLocation,
1213
record,
1314
} = {}) => {
1415
let initialPath = path;
16+
const initialState = state;
1517
if (searchPath) {
1618
// join with & if path contains search query, and ? otherwise
1719
initialPath += path.split("?")[1] ? "&" : "?";
1820
initialPath += searchPath;
1921
}
2022

2123
let [currentPath, currentSearch = ""] = initialPath.split("?");
24+
let currentState = initialState;
2225
const history = [initialPath];
2326
const emitter = mitt();
2427

25-
const navigateImplementation = (path, { replace = false } = {}) => {
28+
const navigateImplementation = (path, { replace = false, state } = {}) => {
2629
if (record) {
2730
if (replace) {
2831
history.splice(history.length - 1, 1, path);
@@ -32,6 +35,7 @@ export const memoryLocation = ({
3235
}
3336

3437
[currentPath, currentSearch = ""] = path.split("?");
38+
if (state !== undefined) currentState = state;
3539
emitter.emit("navigate", path);
3640
};
3741

@@ -56,15 +60,21 @@ export const memoryLocation = ({
5660
function reset() {
5761
// clean history array with mutation to preserve link
5862
history.splice(0, history.length);
59-
60-
navigateImplementation(initialPath);
63+
navigateImplementation(initialPath, { state: initialState });
6164
}
6265

63-
return {
66+
const memoryLocationResult = {
6467
hook: useMemoryLocation,
6568
searchHook: useMemoryQuery,
6669
navigate,
6770
history: record ? history : undefined,
6871
reset: record ? reset : undefined,
6972
};
73+
74+
Object.defineProperty(memoryLocationResult, "state", {
75+
enumerable: true,
76+
get: () => currentState,
77+
});
78+
79+
return memoryLocationResult;
7080
};

packages/wouter/test/memory-location.test-d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ test("should support initial path", () => {
4141
expectTypeOf(hook).toMatchTypeOf<BaseLocationHook>();
4242
});
4343

44+
test("should support state", () => {
45+
const memory = memoryLocation({
46+
path: "/initial-path",
47+
state: { from: "test" },
48+
});
49+
50+
expectTypeOf(memory.state).toEqualTypeOf<{ from: string } | null>();
51+
});
52+
4453
test("should support `static` option", () => {
4554
const { hook } = memoryLocation({ static: true });
4655

packages/wouter/test/memory-location.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ test("should support initial path", () => {
2323
unmount();
2424
});
2525

26+
test("should support initial state", () => {
27+
const memory = memoryLocation({
28+
path: "/test-case",
29+
state: { from: "test" },
30+
});
31+
32+
expect(memory.state).toStrictEqual({ from: "test" });
33+
});
34+
2635
test("should support initial path with query", () => {
2736
const { searchHook } = memoryLocation({ path: "/test-case?foo=bar" });
2837

@@ -78,6 +87,28 @@ test("should return standalone `navigate` method", () => {
7887
unmount();
7988
});
8089

90+
test("should update state through standalone `navigate`", () => {
91+
const memory = memoryLocation({
92+
path: "/test-case",
93+
state: { from: "test" },
94+
});
95+
96+
memory.navigate("/standalone", { state: { from: "navigate" } });
97+
98+
expect(memory.state).toStrictEqual({ from: "navigate" });
99+
});
100+
101+
test("should preserve state when navigating without `state` option", () => {
102+
const memory = memoryLocation({
103+
path: "/test-case",
104+
state: { from: "test" },
105+
});
106+
107+
memory.navigate("/standalone");
108+
109+
expect(memory.state).toStrictEqual({ from: "test" });
110+
});
111+
81112
test("should return location hook that supports navigation", () => {
82113
const { hook } = memoryLocation();
83114

@@ -90,6 +121,20 @@ test("should return location hook that supports navigation", () => {
90121
unmount();
91122
});
92123

124+
test("should update state through hook navigation", () => {
125+
const memory = memoryLocation({
126+
path: "/test-case",
127+
state: { from: "test" },
128+
});
129+
130+
const { result, unmount } = renderHook(() => memory.hook());
131+
132+
act(() => result.current[1]("/location", { state: { from: "hook" } }));
133+
134+
expect(memory.state).toStrictEqual({ from: "hook" });
135+
unmount();
136+
});
137+
93138
test("should record all history when `record` option is provided", () => {
94139
const {
95140
hook,
@@ -161,3 +206,18 @@ test("should have reset method that reset hook location", () => {
161206

162207
unmount();
163208
});
209+
210+
test("should reset state to its initial value", () => {
211+
const memory = memoryLocation({
212+
record: true,
213+
path: "/test",
214+
state: { from: "initial" },
215+
});
216+
217+
memory.navigate("/location", { state: { from: "navigate" } });
218+
expect(memory.state).toStrictEqual({ from: "navigate" });
219+
220+
memory.reset();
221+
222+
expect(memory.state).toStrictEqual({ from: "initial" });
223+
});

packages/wouter/types/memory-location.d.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,25 @@ type Navigate<S = any> = (
1010
options?: { replace?: boolean; state?: S; transition?: boolean }
1111
) => void;
1212

13-
type HookReturnValue = {
13+
type HookReturnValue<S = unknown> = {
1414
hook: BaseLocationHook;
1515
searchHook: BaseSearchHook;
16-
navigate: Navigate;
16+
navigate: Navigate<S>;
17+
readonly state: S | null;
1718
};
1819
type StubHistory = { history: Path[]; reset: () => void };
1920

20-
export function memoryLocation(options?: {
21+
export function memoryLocation<S = unknown>(options?: {
2122
path?: Path;
2223
searchPath?: SearchString;
24+
state?: S;
2325
static?: boolean;
2426
record?: false;
25-
}): HookReturnValue;
26-
export function memoryLocation(options?: {
27+
}): HookReturnValue<S>;
28+
export function memoryLocation<S = unknown>(options?: {
2729
path?: Path;
2830
searchPath?: SearchString;
31+
state?: S;
2932
static?: boolean;
3033
record: true;
31-
}): HookReturnValue & StubHistory;
34+
}): HookReturnValue<S> & StubHistory;

0 commit comments

Comments
 (0)