🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

@thrylm/baqk

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@thrylm/baqk

Smart back navigation with state preservation for React apps

latest
Source
npmnpm
Version
0.1.4
Version published
Weekly downloads
74
54.17%
Maintainers
1
Weekly downloads
 
Created
Source

baqk

Smart back navigation with state preservation for React apps.

npm npm bundle size license

The Problem

User filters a list, clicks into a detail page, hits back — filters are gone. history.back() can't carry state, and sessionStorage alone doesn't know which page to restore. baqk solves this with a hybrid navId + sessionStorage approach that preserves state, scroll position, and navigation context across any number of levels.

Install

npm install @thrylm/baqk

ESM-only. Requires react >= 18.

~3.5 kB gzipped (core + hook). Each adapter adds ~500 B.

Quick Start

// app.tsx — wrap your app with the adapter
import { BaqkAdapter } from "@thrylm/baqk/adapters/react-router";

function App() {
  return (
    <BaqkAdapter>
      <Routes>{/* ... */}</Routes>
    </BaqkAdapter>
  );
}
// products.tsx — listing page
import { useBaqk, useTrailClick } from "@thrylm/baqk";
import { Link } from "react-router-dom";

function ProductList() {
  const { restoredState, saveState } = useBaqk<{ filters: Filters }>();
  const trailClick = useTrailClick("Products");

  // Restore filters synchronously — no useEffect
  const [filters, setFilters] = useState(
    () => restoredState?.filters ?? defaultFilters,
  );

  // Save state whenever filters change
  useEffect(() => { saveState({ filters }); }, [filters]);

  return products.map((p) => (
    <Link to={`/products/${p.id}`} onClick={trailClick}>
      {p.name}
    </Link>
  ));
}
// product-detail.tsx — detail page
import { useBaqk } from "@thrylm/baqk";

function ProductDetail() {
  const { goBack, previousEntry } = useBaqk({
    fallbackPath: "/products",
  });

  return (
    <div>
      <button onClick={() => goBack()}>
        {previousEntry ? `← ${previousEntry.label}` : "← Products"}
      </button>
      {/* ... */}
    </div>
  );
}

Adapters

React Router

import { BaqkAdapter } from "@thrylm/baqk/adapters/react-router";

<BaqkAdapter>
  <RouterProvider router={router} />
</BaqkAdapter>

Next.js App Router

import { BaqkAdapter } from "@thrylm/baqk/adapters/next";

<BaqkAdapter>
  {children}
</BaqkAdapter>

TanStack Router

import { BaqkAdapter } from "@thrylm/baqk/adapters/tanstack";

<BaqkAdapter>
  <RouterProvider router={router} />
</BaqkAdapter>

Generic (any router)

import { BaqkAdapter } from "@thrylm/baqk";

<BaqkAdapter
  navigate={(path, options) =>
    options?.replace ? myRouter.replace(path) : myRouter.push(path)
  }
  getCurrentPath={() => window.location.pathname + window.location.search}
>
  {children}
</BaqkAdapter>

All router-specific adapters accept optional sessionKey and storage props. The generic adapter additionally requires navigate and getCurrentPath.

API Reference

useTrailClick(label?)

Returns an onClick handler that pushes a trail entry without navigating. Attach it to same-tab internal <Link>/anchor navigations — the link handles navigation natively, no preventDefault needed.

import { useTrailClick } from "@thrylm/baqk";

function ProductList() {
  const trailClick = useTrailClick("Products");

  return products.map((p) => (
    <Link to={`/products/${p.id}`} onClick={trailClick}>
      {p.name}
    </Link>
  ));
}

Behavior:

  • Saves scroll position and pushes the current page onto the trail
  • Skips on modifier keys (meta, ctrl, shift, alt), middle-click, or defaultPrevented
  • Skips new-tab, download, external, and hash-only anchor clicks
  • Captures path at click time via getCurrentPath() (reads window.location, compatible with nuqs/shallow updates)
  • Does NOT call preventDefault() or navigate — the underlying link handles that
  • Shares the same navId as useBaqk() (both use ensureNavId which is idempotent)

useBaqk<T>(options?)

Options (BaqkOptions)

OptionTypeDefaultDescription
fallbackPathstringundefinedPath to navigate to when goBack() is called with no trail
autoSaveScrollbooleantrueAutomatically save/restore scroll position

Return value (BaqkResult<T>)

PropertyTypeDescription
goBack(fallbackPath?) => voidPop the trail and navigate back, or use fallback
previousEntryTrailEntry | nullThe most recent trail entry (the page you'd go back to)
saveState(state: T) => voidSave state for the current page
restoredStateT | nullSynchronously available saved state (lazy ref pattern)
wasRestoredbooleanWhether state was restored for this page
clear() => voidClear the trail and all associated state

TrailEntry

interface TrailEntry {
  path: string;
  navId: string;
  label?: string;
  timestamp: number;
}

BaqkAdapterProps (router-specific adapters)

PropTypeDescription
childrenReactNode
sessionKeystring?Namespace for storage isolation (e.g. user ID)
storageStorageAdapter?Custom storage backend (defaults to sessionStorage)

GenericBaqkAdapterProps (generic adapter)

Extends BaqkAdapterProps with:

PropTypeDescription
navigate(path: string, options?: { replace?: boolean }) => voidNavigation function
getCurrentPath() => stringReturns current path + search params

How It Works

  • Each page visit gets a unique navId stamped into history.state
  • When useTrailClick fires, the current page's path + navId are pushed onto a trail stack in sessionStorage
  • State and scroll position are keyed by sessionKey:navId, so they survive navigations
  • goBack pops the trail, navigates to the previous path, and re-stamps the navId — triggering automatic state + scroll restoration
  • restoredState is computed synchronously via a lazy ref (no useEffect, no flash of default state)

Advanced

Session key

Use sessionKey to isolate trails per user or session:

<BaqkAdapter sessionKey={user.id}>

Custom storage

import { createMemoryStorage } from "@thrylm/baqk";

<BaqkAdapter storage={createMemoryStorage()}>

createBaqkAdapter factory

Build an adapter for any router:

import { createBaqkAdapter } from "@thrylm/baqk";

const MyBaqkAdapter = createBaqkAdapter(() => {
  // Return a RouterAdapter: { getCurrentPath, navigate, getHistoryState, replaceHistoryState }
  return useMyRouter();
});

Limits

  • Trail stack: max 50 entries (oldest evicted with their state)
  • State size: max 100 KB per entry (oversized state is silently dropped with a console warning)

License

MIT

Keywords

react

FAQs

Package last updated on 16 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts