CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-reach--router

Next generation React routing library with accessible navigation and focus management.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

history.mddocs/

History Management

History utilities for creating custom history instances and managing navigation state in different environments.

Capabilities

Global History

Default history instance that uses browser history or memory fallback for navigation state management.

/**
 * Default history instance using browser history or memory fallback
 * Available as a singleton for the entire application
 */
const globalHistory: History;

interface History {
  location: Location;
  transitioning: boolean;
  listen(listener: (event: HistoryEvent) => void): () => void;
  navigate(to: string | number, options?: NavigateOptions): Promise<void>;
  _onTransitionComplete(): void;
}

interface HistoryEvent {
  location: Location;
  action: "PUSH" | "POP";
}

Usage Examples:

import { globalHistory } from "@reach/router";

// Access current location
console.log(globalHistory.location.pathname);

// Listen to navigation changes
const unlisten = globalHistory.listen(({ location, action }) => {
  console.log(`Navigation ${action} to ${location.pathname}`);
});

// Clean up listener
unlisten();

// Direct navigation
globalHistory.navigate("/about");

// Check if navigation is in progress
if (globalHistory.transitioning) {
  console.log("Navigation in progress...");
}

// Manual transition completion (rarely needed)
globalHistory._onTransitionComplete();

Create History Function

Factory function for creating custom history instances with different location sources.

/**
 * Creates a custom history instance with specified source
 * @param source - Location source (window or memory source)
 * @param options - Additional configuration options
 * @returns Custom history instance
 */
function createHistory(source: LocationSource, options?: any): History;

interface LocationSource {
  location: {
    pathname: string;
    search: string;
    hash?: string;
    href?: string;
    origin?: string;
    protocol?: string;
    host?: string;
    hostname?: string;
    port?: string;
  };
  history: {
    state: any;
    pushState(state: any, title: string | null, url: string): void;
    replaceState(state: any, title: string | null, url: string): void;
    go(delta: number): void;
  };
  addEventListener(type: string, listener: () => void): void;
  removeEventListener(type: string, listener: () => void): void;
}

Usage Examples:

import { createHistory, createMemorySource } from "@reach/router";

// Create history with memory source for testing
const testHistory = createHistory(createMemorySource("/initial-path"));

// Create history with custom window-like source
const customSource = {
  location: { pathname: "/custom", search: "" },
  history: {
    state: null,
    pushState: (state, title, url) => console.log("Push:", url),
    replaceState: (state, title, url) => console.log("Replace:", url),
    go: (delta) => console.log("Go:", delta)
  },
  addEventListener: (type, listener) => {},
  removeEventListener: (type, listener) => {}
};

const customHistory = createHistory(customSource);

// Use custom history with LocationProvider
import { LocationProvider } from "@reach/router";

const TestApp = () => (
  <LocationProvider history={testHistory}>
    <App />
  </LocationProvider>
);

// History for specific features
const createFeatureHistory = (initialPath) => {
  const source = createMemorySource(initialPath);
  return createHistory(source);
};

const modalHistory = createFeatureHistory("/modal/welcome");
const wizardHistory = createFeatureHistory("/wizard/step1");

Create Memory Source Function

Creates an in-memory location source for testing or non-browser environments.

/**
 * Creates an in-memory location source for testing or non-browser environments
 * @param initialPath - Starting path for the memory source
 * @returns Memory source compatible with createHistory
 */
function createMemorySource(initialPath?: string): LocationSource;

interface MemorySource extends LocationSource {
  history: {
    entries: Array<{ pathname: string; search: string }>;
    index: number;
    state: any;
    pushState(state: any, title: string | null, url: string): void;
    replaceState(state: any, title: string | null, url: string): void;
    go(delta: number): void;
  };
}

Usage Examples:

import { createMemorySource, createHistory } from "@reach/router";

// Basic memory source
const memorySource = createMemorySource("/home");
const memoryHistory = createHistory(memorySource);

// Memory source with query parameters
const sourceWithQuery = createMemorySource("/search?q=react&page=1");

// Testing navigation sequences
const testNavigationSequence = async () => {
  const source = createMemorySource("/");
  const history = createHistory(source);
  
  // Navigate to different pages
  await history.navigate("/about");
  await history.navigate("/contact");
  await history.navigate("/products/123");
  
  // Check history entries
  console.log(source.history.entries);
  console.log("Current index:", source.history.index);
  
  // Navigate back
  history.navigate(-1);
  console.log("After back:", history.location.pathname);
};

// Memory source for isolated testing
const createIsolatedTest = (initialPath) => {
  const source = createMemorySource(initialPath);
  const history = createHistory(source);
  
  return {
    history,
    navigate: history.navigate,
    getPath: () => history.location.pathname,
    getEntries: () => source.history.entries,
    getCurrentIndex: () => source.history.index
  };
};

// Usage in tests
const test = createIsolatedTest("/app");
await test.navigate("/users/123");
await test.navigate("/users/123/edit");
test.navigate(-1); // Back to /users/123
console.log(test.getPath()); // "/users/123"

// React Native or server environments
const createServerHistory = (initialUrl) => {
  const source = createMemorySource(initialUrl);
  return createHistory(source);
};

const serverHistory = createServerHistory("/api/v1/users");

Navigation Promise Handling

Understanding how navigation promises work and handling transition states.

/**
 * Navigation returns promises for handling async transitions
 */
interface NavigationPromise extends Promise<void> {
  // Resolves when navigation and focus management complete
  // Useful for waiting on navigation in tests or complex flows
}

Usage Examples:

import { navigate, globalHistory } from "@reach/router";

// Wait for navigation completion
const handleLogin = async (credentials) => {
  const user = await login(credentials);
  if (user) {
    // Wait for navigation to complete before showing success message
    await navigate("/dashboard");
    showSuccessMessage("Welcome back!");
  }
};

// Sequential navigation
const wizardFlow = async () => {
  await navigate("/wizard/step1");
  // Wait for user input...
  await navigate("/wizard/step2");
  // Wait for user input...
  await navigate("/wizard/complete");
};

// Navigation with loading states
const NavigationButton = ({ to, children }) => {
  const [navigating, setNavigating] = React.useState(false);
  
  const handleClick = async () => {
    setNavigating(true);
    try {
      await navigate(to);
    } finally {
      setNavigating(false);
    }
  };
  
  return (
    <button onClick={handleClick} disabled={navigating}>
      {navigating ? "Navigating..." : children}
    </button>
  );
};

// Transition monitoring
const TransitionMonitor = () => {
  const [isTransitioning, setIsTransitioning] = React.useState(false);
  
  React.useEffect(() => {
    const checkTransition = () => {
      setIsTransitioning(globalHistory.transitioning);
    };
    
    const unlisten = globalHistory.listen(checkTransition);
    checkTransition(); // Initial check
    
    return unlisten;
  }, []);
  
  return isTransitioning ? <div>Loading...</div> : null;
};

// Error handling during navigation
const safeNavigate = async (to, options = {}) => {
  try {
    await navigate(to, options);
  } catch (error) {
    console.error("Navigation failed:", error);
    // Handle navigation errors
    await navigate("/error", { 
      state: { originalDestination: to, error: error.message }
    });
  }
};

History Integration Patterns

Common patterns for integrating history with application state and external systems.

Usage Examples:

// History synchronization with external state
const createSyncedHistory = (externalStore) => {
  const history = createHistory(createMemorySource("/"));
  
  // Sync external state changes to history
  externalStore.subscribe((state) => {
    const newPath = state.currentRoute || "/";
    if (history.location.pathname !== newPath) {
      history.navigate(newPath, { replace: true });
    }
  });
  
  // Sync history changes to external state
  history.listen(({ location }) => {
    externalStore.dispatch({
      type: "ROUTE_CHANGED",
      payload: location.pathname
    });
  });
  
  return history;
};

// History with persistent state
const createPersistentHistory = (storageKey) => {
  const initialPath = localStorage.getItem(storageKey) || "/";
  const history = createHistory(createMemorySource(initialPath));
  
  history.listen(({ location }) => {
    localStorage.setItem(storageKey, location.pathname);
  });
  
  return history;
};

// Multi-frame history coordination
const createCoordinatedHistory = () => {
  const history = createHistory(createMemorySource("/"));
  
  // Listen for messages from other frames
  window.addEventListener("message", (event) => {
    if (event.data.type === "NAVIGATE") {
      history.navigate(event.data.path);
    }
  });
  
  // Broadcast navigation to other frames
  history.listen(({ location }) => {
    window.parent.postMessage({
      type: "LOCATION_CHANGED",
      path: location.pathname
    }, "*");
  });
  
  return history;
};

// History with undo/redo functionality
const createUndoableHistory = () => {
  const source = createMemorySource("/");
  const history = createHistory(source);
  
  const undoStack = [];
  const redoStack = [];
  
  const originalNavigate = history.navigate;
  
  history.navigate = (to, options = {}) => {
    if (typeof to === "string") {
      undoStack.push(history.location.pathname);
      redoStack.length = 0; // Clear redo stack
    }
    return originalNavigate(to, options);
  };
  
  history.undo = () => {
    if (undoStack.length > 0) {
      const previousPath = undoStack.pop();
      redoStack.push(history.location.pathname);
      return originalNavigate(previousPath, { replace: true });
    }
  };
  
  history.redo = () => {
    if (redoStack.length > 0) {
      const nextPath = redoStack.pop();
      undoStack.push(history.location.pathname);
      return originalNavigate(nextPath, { replace: true });
    }
  };
  
  return history;
};

Install with Tessl CLI

npx tessl i tessl/npm-reach--router

docs

advanced-routing.md

history.md

hooks.md

index.md

location-context.md

navigation.md

routing.md

server-rendering.md

utilities.md

tile.json