0
# History Management
1
2
History utilities for creating custom history instances and managing navigation state in different environments.
3
4
## Capabilities
5
6
### Global History
7
8
Default history instance that uses browser history or memory fallback for navigation state management.
9
10
```javascript { .api }
11
/**
12
* Default history instance using browser history or memory fallback
13
* Available as a singleton for the entire application
14
*/
15
const globalHistory: History;
16
17
interface History {
18
location: Location;
19
transitioning: boolean;
20
listen(listener: (event: HistoryEvent) => void): () => void;
21
navigate(to: string | number, options?: NavigateOptions): Promise<void>;
22
_onTransitionComplete(): void;
23
}
24
25
interface HistoryEvent {
26
location: Location;
27
action: "PUSH" | "POP";
28
}
29
```
30
31
**Usage Examples:**
32
33
```javascript
34
import { globalHistory } from "@reach/router";
35
36
// Access current location
37
console.log(globalHistory.location.pathname);
38
39
// Listen to navigation changes
40
const unlisten = globalHistory.listen(({ location, action }) => {
41
console.log(`Navigation ${action} to ${location.pathname}`);
42
});
43
44
// Clean up listener
45
unlisten();
46
47
// Direct navigation
48
globalHistory.navigate("/about");
49
50
// Check if navigation is in progress
51
if (globalHistory.transitioning) {
52
console.log("Navigation in progress...");
53
}
54
55
// Manual transition completion (rarely needed)
56
globalHistory._onTransitionComplete();
57
```
58
59
### Create History Function
60
61
Factory function for creating custom history instances with different location sources.
62
63
```javascript { .api }
64
/**
65
* Creates a custom history instance with specified source
66
* @param source - Location source (window or memory source)
67
* @param options - Additional configuration options
68
* @returns Custom history instance
69
*/
70
function createHistory(source: LocationSource, options?: any): History;
71
72
interface LocationSource {
73
location: {
74
pathname: string;
75
search: string;
76
hash?: string;
77
href?: string;
78
origin?: string;
79
protocol?: string;
80
host?: string;
81
hostname?: string;
82
port?: string;
83
};
84
history: {
85
state: any;
86
pushState(state: any, title: string | null, url: string): void;
87
replaceState(state: any, title: string | null, url: string): void;
88
go(delta: number): void;
89
};
90
addEventListener(type: string, listener: () => void): void;
91
removeEventListener(type: string, listener: () => void): void;
92
}
93
```
94
95
**Usage Examples:**
96
97
```javascript
98
import { createHistory, createMemorySource } from "@reach/router";
99
100
// Create history with memory source for testing
101
const testHistory = createHistory(createMemorySource("/initial-path"));
102
103
// Create history with custom window-like source
104
const customSource = {
105
location: { pathname: "/custom", search: "" },
106
history: {
107
state: null,
108
pushState: (state, title, url) => console.log("Push:", url),
109
replaceState: (state, title, url) => console.log("Replace:", url),
110
go: (delta) => console.log("Go:", delta)
111
},
112
addEventListener: (type, listener) => {},
113
removeEventListener: (type, listener) => {}
114
};
115
116
const customHistory = createHistory(customSource);
117
118
// Use custom history with LocationProvider
119
import { LocationProvider } from "@reach/router";
120
121
const TestApp = () => (
122
<LocationProvider history={testHistory}>
123
<App />
124
</LocationProvider>
125
);
126
127
// History for specific features
128
const createFeatureHistory = (initialPath) => {
129
const source = createMemorySource(initialPath);
130
return createHistory(source);
131
};
132
133
const modalHistory = createFeatureHistory("/modal/welcome");
134
const wizardHistory = createFeatureHistory("/wizard/step1");
135
```
136
137
### Create Memory Source Function
138
139
Creates an in-memory location source for testing or non-browser environments.
140
141
```javascript { .api }
142
/**
143
* Creates an in-memory location source for testing or non-browser environments
144
* @param initialPath - Starting path for the memory source
145
* @returns Memory source compatible with createHistory
146
*/
147
function createMemorySource(initialPath?: string): LocationSource;
148
149
interface MemorySource extends LocationSource {
150
history: {
151
entries: Array<{ pathname: string; search: string }>;
152
index: number;
153
state: any;
154
pushState(state: any, title: string | null, url: string): void;
155
replaceState(state: any, title: string | null, url: string): void;
156
go(delta: number): void;
157
};
158
}
159
```
160
161
**Usage Examples:**
162
163
```javascript
164
import { createMemorySource, createHistory } from "@reach/router";
165
166
// Basic memory source
167
const memorySource = createMemorySource("/home");
168
const memoryHistory = createHistory(memorySource);
169
170
// Memory source with query parameters
171
const sourceWithQuery = createMemorySource("/search?q=react&page=1");
172
173
// Testing navigation sequences
174
const testNavigationSequence = async () => {
175
const source = createMemorySource("/");
176
const history = createHistory(source);
177
178
// Navigate to different pages
179
await history.navigate("/about");
180
await history.navigate("/contact");
181
await history.navigate("/products/123");
182
183
// Check history entries
184
console.log(source.history.entries);
185
console.log("Current index:", source.history.index);
186
187
// Navigate back
188
history.navigate(-1);
189
console.log("After back:", history.location.pathname);
190
};
191
192
// Memory source for isolated testing
193
const createIsolatedTest = (initialPath) => {
194
const source = createMemorySource(initialPath);
195
const history = createHistory(source);
196
197
return {
198
history,
199
navigate: history.navigate,
200
getPath: () => history.location.pathname,
201
getEntries: () => source.history.entries,
202
getCurrentIndex: () => source.history.index
203
};
204
};
205
206
// Usage in tests
207
const test = createIsolatedTest("/app");
208
await test.navigate("/users/123");
209
await test.navigate("/users/123/edit");
210
test.navigate(-1); // Back to /users/123
211
console.log(test.getPath()); // "/users/123"
212
213
// React Native or server environments
214
const createServerHistory = (initialUrl) => {
215
const source = createMemorySource(initialUrl);
216
return createHistory(source);
217
};
218
219
const serverHistory = createServerHistory("/api/v1/users");
220
```
221
222
### Navigation Promise Handling
223
224
Understanding how navigation promises work and handling transition states.
225
226
```javascript { .api }
227
/**
228
* Navigation returns promises for handling async transitions
229
*/
230
interface NavigationPromise extends Promise<void> {
231
// Resolves when navigation and focus management complete
232
// Useful for waiting on navigation in tests or complex flows
233
}
234
```
235
236
**Usage Examples:**
237
238
```javascript
239
import { navigate, globalHistory } from "@reach/router";
240
241
// Wait for navigation completion
242
const handleLogin = async (credentials) => {
243
const user = await login(credentials);
244
if (user) {
245
// Wait for navigation to complete before showing success message
246
await navigate("/dashboard");
247
showSuccessMessage("Welcome back!");
248
}
249
};
250
251
// Sequential navigation
252
const wizardFlow = async () => {
253
await navigate("/wizard/step1");
254
// Wait for user input...
255
await navigate("/wizard/step2");
256
// Wait for user input...
257
await navigate("/wizard/complete");
258
};
259
260
// Navigation with loading states
261
const NavigationButton = ({ to, children }) => {
262
const [navigating, setNavigating] = React.useState(false);
263
264
const handleClick = async () => {
265
setNavigating(true);
266
try {
267
await navigate(to);
268
} finally {
269
setNavigating(false);
270
}
271
};
272
273
return (
274
<button onClick={handleClick} disabled={navigating}>
275
{navigating ? "Navigating..." : children}
276
</button>
277
);
278
};
279
280
// Transition monitoring
281
const TransitionMonitor = () => {
282
const [isTransitioning, setIsTransitioning] = React.useState(false);
283
284
React.useEffect(() => {
285
const checkTransition = () => {
286
setIsTransitioning(globalHistory.transitioning);
287
};
288
289
const unlisten = globalHistory.listen(checkTransition);
290
checkTransition(); // Initial check
291
292
return unlisten;
293
}, []);
294
295
return isTransitioning ? <div>Loading...</div> : null;
296
};
297
298
// Error handling during navigation
299
const safeNavigate = async (to, options = {}) => {
300
try {
301
await navigate(to, options);
302
} catch (error) {
303
console.error("Navigation failed:", error);
304
// Handle navigation errors
305
await navigate("/error", {
306
state: { originalDestination: to, error: error.message }
307
});
308
}
309
};
310
```
311
312
### History Integration Patterns
313
314
Common patterns for integrating history with application state and external systems.
315
316
**Usage Examples:**
317
318
```javascript
319
// History synchronization with external state
320
const createSyncedHistory = (externalStore) => {
321
const history = createHistory(createMemorySource("/"));
322
323
// Sync external state changes to history
324
externalStore.subscribe((state) => {
325
const newPath = state.currentRoute || "/";
326
if (history.location.pathname !== newPath) {
327
history.navigate(newPath, { replace: true });
328
}
329
});
330
331
// Sync history changes to external state
332
history.listen(({ location }) => {
333
externalStore.dispatch({
334
type: "ROUTE_CHANGED",
335
payload: location.pathname
336
});
337
});
338
339
return history;
340
};
341
342
// History with persistent state
343
const createPersistentHistory = (storageKey) => {
344
const initialPath = localStorage.getItem(storageKey) || "/";
345
const history = createHistory(createMemorySource(initialPath));
346
347
history.listen(({ location }) => {
348
localStorage.setItem(storageKey, location.pathname);
349
});
350
351
return history;
352
};
353
354
// Multi-frame history coordination
355
const createCoordinatedHistory = () => {
356
const history = createHistory(createMemorySource("/"));
357
358
// Listen for messages from other frames
359
window.addEventListener("message", (event) => {
360
if (event.data.type === "NAVIGATE") {
361
history.navigate(event.data.path);
362
}
363
});
364
365
// Broadcast navigation to other frames
366
history.listen(({ location }) => {
367
window.parent.postMessage({
368
type: "LOCATION_CHANGED",
369
path: location.pathname
370
}, "*");
371
});
372
373
return history;
374
};
375
376
// History with undo/redo functionality
377
const createUndoableHistory = () => {
378
const source = createMemorySource("/");
379
const history = createHistory(source);
380
381
const undoStack = [];
382
const redoStack = [];
383
384
const originalNavigate = history.navigate;
385
386
history.navigate = (to, options = {}) => {
387
if (typeof to === "string") {
388
undoStack.push(history.location.pathname);
389
redoStack.length = 0; // Clear redo stack
390
}
391
return originalNavigate(to, options);
392
};
393
394
history.undo = () => {
395
if (undoStack.length > 0) {
396
const previousPath = undoStack.pop();
397
redoStack.push(history.location.pathname);
398
return originalNavigate(previousPath, { replace: true });
399
}
400
};
401
402
history.redo = () => {
403
if (redoStack.length > 0) {
404
const nextPath = redoStack.pop();
405
undoStack.push(history.location.pathname);
406
return originalNavigate(nextPath, { replace: true });
407
}
408
};
409
410
return history;
411
};
412
```