input-handling

star 416

Reconcile instant local input feedback with URL write throttling. setState updates internal state synchronously (instant re-render); setUrl is throttled and asynchronous (URL catches up on the next tick). Use this skill for text inputs, search boxes, sliders, range pickers, and any control that fires many updates per second where binding setUrl directly to onChange causes perceived lag or wasted URL writes.

asmyshlyaev177 By asmyshlyaev177 schedule Updated 5/24/2026

name: input-handling description: > Reconcile instant local input feedback with URL write throttling. setState updates internal state synchronously (instant re-render); setUrl is throttled and asynchronous (URL catches up on the next tick). Use this skill for text inputs, search boxes, sliders, range pickers, and any control that fires many updates per second where binding setUrl directly to onChange causes perceived lag or wasted URL writes. type: core library: state-in-url library_version: '6.1.3' requires: - feature-state-hook sources: - 'asmyshlyaev177/state-in-url:packages/urlstate/useUrlStateBase/useUrlStateBase.ts' - 'asmyshlyaev177/state-in-url:packages/urlstate/utils.ts' - 'asmyshlyaev177/state-in-url:README.md#update-state-only-and-sync-to-url-manually'

This skill builds on state-in-url/feature-state-hook. Read it first for the module-scoped default-state rule.

state-in-url — Input handling

useUrlState returns three setters with different timing:

Setter What updates When
setState(value) Internal state only Synchronous
setUrl(value) Internal state + URL State sync, URL on next tick (throttled)
setUrl() Flush current state to URL URL on next tick (diff-checked, no-op if equal)

State updates always render immediately. URL writes are coalesced through an internal global timer (TIMEOUT constant in useUrlStateBase.ts) so a burst of setUrl calls produces one URL update.

Setup

// features/search/searchState.ts
export type SearchState = { q: string; sort: 'name' | 'date' };
export const SEARCH_STATE: SearchState = { q: '', sort: 'name' };
// features/search/useSearchState.ts
'use client';
import { useSearchParams } from 'next/navigation';
import { useUrlState } from 'state-in-url/next';
import { SEARCH_STATE } from './searchState';

export function useSearchState() {
  const searchParams = useSearchParams();
  return useUrlState(SEARCH_STATE, { searchParams });
}

Core Patterns

Instant input, deferred URL write (onBlur)

function SearchBox() {
  const { urlState, setState, setUrl } = useSearchState();

  return (
    <input
      value={urlState.q}
      onChange={(e) => setState({ q: e.target.value })}
      onBlur={() => setUrl()}
    />
  );
}

setState updates state instantly (input feels native). setUrl() with no args flushes current state to the URL when the user finishes typing.

Discrete controls — setUrl directly

For click/select/toggle controls that fire at most a few times per second, skip the split. setUrl is already throttled.

<button onClick={() => setUrl({ sort: 'date' })}>Sort by date</button>

Common Mistakes

CRITICAL setUrl inside useEffect → infinite update loop

(Cross-skill failure — also in feature-state-hook.)

Wrong:

React.useEffect(() => {
  setUrl({ q: urlState.q.trim() }); // re-fires every URL change
}, [urlState, setUrl]);

Correct:

React.useEffect(() => {
  const trimmed = urlState.q.trim();
  if (urlState.q !== trimmed) setUrl({ q: trimmed });
}, [urlState.q, setUrl]);

URL throttling does not break a state→effect→setUrl→state cycle.

Source: Maintainer interview

HIGH Binding setUrl directly to onChange of a typing input

Wrong:

<input
  value={urlState.q}
  onChange={(e) => setUrl({ q: e.target.value })}
/>

Correct:

<input
  value={urlState.q}
  onChange={(e) => setState({ q: e.target.value })}
  onBlur={() => setUrl()}
/>

Every keystroke fires a URL update. The library coalesces them with an internal timer, but the perceived behavior is laggy URL with extra rerenders (one for state, another when URL settles). Issue #78 was filed exactly for this symptom — the throttling is intentional.

Source: GitHub issue #78 (asmyshlyaev177/state-in-url); README "Update state only and sync to URL manually"

MEDIUM Expecting window.location.search to reflect setUrl synchronously

Wrong:

setUrl({ tab: 'b' });
console.log(window.location.search); // still the old value

Correct:

setUrl({ tab: 'b' });
// `urlState.tab` already equals 'b' synchronously.
// `window.location.search` catches up after the throttle tick.

setUrl is "last-write-wins" — it coalesces a burst of updates into one URL write on the next macrotask. Read urlState, not window.location.

Source: useUrlStateBase.ts (global timer); GitHub issue #78

See also

  • state-in-url/feature-state-hook — base pattern this skill builds on.
  • state-in-url/nextjs-ssr — for SSR-safe wiring of the search input on Next.js App Router.
Install via CLI
npx skills add https://github.com/asmyshlyaev177/state-in-url --skill input-handling
Repository Details
star Stars 416
call_split Forks 10
navigation Branch main
article Path SKILL.md
More from Creator
asmyshlyaev177
asmyshlyaev177 Explore all skills →