Open Source

use-address-state.

A lightweight React library for managing state in URL search params using useSyncExternalStore.

YearJan 10, 2026
AuthorVinod Liyanage
Open SourceReactTypeScriptnpmGithub
use-address-state

The Origin Story

It started with a bookmark manager.

While building Luma Bookmarks—a folder-based bookmark organizer with Google Drive sync—I needed a way to handle folder navigation. Users would click into nested folders, and I wanted that navigation to feel native: back button works, refresh preserves position, and shareable links that open exactly where you left off.

The solution? Treat the URL as state.

I built a small hook that synced folder paths with the URL. It worked beautifully. But as the codebase grew, I noticed something: this pattern was solving more than just navigation. It was handling search filters, modal states, pagination—anything that should survive a refresh or be shareable.

So I extracted it. Polished it. Published it.

Luma Bookmarks Folder Navigation

The folder navigation system in Luma Bookmarks. Every folder path syncs with the URL, enabling deep-linking and browser history support.


The Philosophy

What if the URL was more than just an address?

Every time you share a link, you're sharing a snapshot of state. The tab you have open. The filter you applied. The search term you typed. The browser has always known this—URLs survive refreshes, work with back/forward navigation, and can be copied into a message.

But React apps treat the URL like an afterthought. State lives in useState, dies with the tab, and can't be shared.

I wanted to bridge that gap.


The Problem

Syncing React state with URL search params is surprisingly tedious:

  • Boilerplate everywhere — The same URLSearchParams parsing logic repeated across components
  • Prop drilling hell — Multiple components need the same URL state? Time to lift state up
  • Router lock-in — Most solutions tie you to React Router or Next.js
  • Hydration nightmares — Server-side rendering introduces subtle mismatches
  • Re-render chaos — Updating one param shouldn't re-render components watching other params

For something so fundamental, there had to be a simpler way.


The Solution

One hook. Familiar API. Zero dependencies.

import { useAddressState } from "use-address-state";
 
function SearchPage() {
  const [query, setQuery] = useAddressState("q");
 
  return (
    <input
      value={query || ""}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search..."
    />
  );
}

That's it. Now query is synced with ?q=... in the URL. Update it, and the URL updates. Share the link, and anyone opening it gets the same state.


How It Works

The magic is in three pieces:

1. Reads from the URL Using window.location.search and URLSearchParams, the hook parses the current value for your key.

2. Writes with History API Updates use history.pushState() directly—no router dependency, no page navigation, just clean URL updates.

3. Syncs with useSyncExternalStore React 18's useSyncExternalStore ensures tear-free reads and proper concurrent mode support. Components subscribed to different keys won't re-render each other.


Selective Re-rendering

Here's where it gets interesting. Components only re-render when their specific key changes:

function CounterA() {
  const [count, setCount] = useAddressState("a", 0);
  // Only re-renders when 'a' changes
  return <button onClick={() => setCount(count + 1)}>A: {count}</button>;
}
 
function CounterB() {
  const [count, setCount] = useAddressState("b", 0);
  // Only re-renders when 'b' changes
  return <button onClick={() => setCount(count + 1)}>B: {count}</button>;
}

Click button A a hundred times—button B sits quietly, untouched.


Shared State Without Prop Drilling

Components using the same key automatically share state:

// header.tsx
function SearchBar() {
  const [query, setQuery] = useAddressState("q");
  return <input value={query || ""} onChange={(e) => setQuery(e.target.value)} />;
}
 
// results.tsx
function SearchResults() {
  const [query] = useAddressState("q");
  return <div>Showing results for: {query}</div>;
}

No context providers. No props. No Redux. Just the URL as your single source of truth.


Perfect For

  • Search inputs and filters — Persist and share search queries
  • Pagination?page=3 that survives refreshes
  • Tab and accordion state — Deep-linkable UI sections
  • Modal states?modal=settings for shareable dialogs
  • Form wizards?step=2 for multi-step flows
  • Any UI state worth sharing — If it should survive a refresh, put it in the URL

The Constraints

Being honest about limitations:

  • Client-side only — The URL is a browser concept; SSR gets initial values but updates happen client-side
  • String-based storage — Values are JSON-serialized; not ideal for complex objects
  • URL length limits — Keep it under ~2000 characters for browser compatibility

This isn't meant to replace all state management. It's for the specific case where you want state in the URL.


The Technical Details

MetricValue
Bundle Size~1KB minified
DependenciesZero
React Version18.0.0+
TypeScriptFull support
SSRSafe with fallbacks

Works with Create React App, Vite, Next.js, Remix—anything that runs React in a browser.


Try It

pnpm add use-address-state