Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nuejs/nue/llms.txt

Use this file to discover all available pages before exploring further.

Nuestate is a reactive state container that treats the URL as the primary store. When you write state.view = 'grid', the URL updates automatically. When the user hits the browser back button, state restores from the URL. Bookmarks, link sharing, and history navigation all work without any extra plumbing.

Installation

bun add nuestate
# or
npm install nuestate

Import

import { state } from 'nuestate'
The exported state object is a Proxy over an internal API. Reading state.foo returns the current value of foo from whichever storage context it lives in. Writing state.foo = value persists the value and fires any registered listeners.

state.setup(opts)

Configure the state object before use. Defines which keys live in the URL path, the query string, session storage, or local storage. Must be called once at application startup.
state.setup({
  route: '/app/:section/:id',
  query: ['search', 'filter', 'page'],
  session: ['user', 'token'],
  local: ['theme', 'language'],
  memory: ['tempData'],
  autolink: true
})
route
string
default:"\"\""
An Express-style path template. Segments prefixed with : become named route parameters. When the matching parameters change, history.pushState is called with the new path.
// /app/:section/:id
state.section = 'users'
state.id = '42'
// URL becomes: /app/users/42
query
string[]
default:"[]"
Keys whose values are stored in the URL query string. Changing any of these keys calls history.replaceState with the new search string.
// query: ['search', 'page']
state.search = 'nue'
state.page = 2
// URL becomes: ?search=nue&page=2
session
string[]
Keys persisted in sessionStorage. Values survive page refreshes within the same browser tab but are cleared when the tab closes.
local
string[]
Keys persisted in localStorage. Values survive across tabs and browser restarts.
memory
string[]
default:"[]"
Keys kept only in JavaScript memory. Values are lost on a full page reload. Useful for in-page transient state that should not appear in the URL.
When true, attaches a global click listener to document. Clicks on <a href> elements whose pathname matches the configured route are intercepted and handled as SPA navigation — no full-page reload.
state.setup({ route: '/app/:page', autolink: true })
// <a href="/app/settings"> now navigates without reload
emit_only
string[]
Keys that can only be changed via state.emit(). Writes via state.key = value are ignored for these keys. Useful for one-way event channels.

Reading state

Use state.key to read the current value of any configured key. The proxy reads from the appropriate storage automatically — URL params, query string, session storage, and local storage are all merged:
const view = state.view
const userId = state.id
The composite state.data getter returns all current state as a plain object:
const snapshot = state.data
// { section: 'users', id: '42', search: 'nue', theme: 'dark', ... }
Nuestate automatically coerces string values from the URL and storage. "true" becomes true, "false" becomes false, and numeric strings become numbers. "null" and null are deleted from the state object.

Writing state

Assign directly to state.key. The proxy calls state.set() internally, persists the value to the correct store, and fires registered listeners:
state.view = 'grid'       // persisted according to setup()
state.search = 'hello'    // URL query string updates
state.theme = 'dark'      // localStorage if 'theme' is in local[]
You cannot override built-in API methods (setup, on, off, emit, set, data, init, clear) by assignment. Nuestate logs a console error and ignores the write.

state.on(events, handler)

Register a listener that fires whenever one of the named state keys changes.
state.on(events, handler)
events
string
required
A space-separated list of key names to watch. The handler fires when any of the listed keys is included in a change batch.
state.on('search filter', handleSearch)
state.on('theme', applyTheme)
handler
function
required
Called with a changes object whose keys are the state keys that changed and whose values are the new values.
state.on('search filter page', async (changes) => {
  const { search, filter, page } = changes
  const results = await fetchResults(search, filter, page)
  state.results = results
})
Registering the same handler (by source text) for the same event string replaces the previous registration rather than stacking:
state.on('query', handler)  // register
state.on('query', handler)  // replaces — not duplicated

state.off(events, handler)

Remove a previously registered listener.
state.off('search filter', handleSearch)
events
string
required
Must match the exact string passed to state.on.
handler
function
required
Must be the same function reference (or a function with identical source text) passed to state.on.

state.emit(name, value)

Trigger listeners for a key that is in the emit_only list, without persisting anything to storage or the URL.
state.emit(name, value)
name
string
required
The key name. Must be included in emit_only in the setup options, otherwise this call is a no-op.
value
any
required
The value dispatched to listeners. Not persisted anywhere.
Example:
state.setup({ emit_only: ['notification'] })

state.on('notification', ({ notification }) => {
  showToast(notification)
})

// Somewhere else in the app:
state.emit('notification', { type: 'success', text: 'Saved!' })

state.set(data, is_popstate?)

Low-level batch update. Compares data against current state, persists changed keys to the appropriate stores, fires listeners for the changed keys, and pushes a new URL history entry when path or query params change.
state.set({ section: 'products', search: 'shoes' })
data
object
required
An object of key–value pairs to apply. Only keys that are actually configured (in route, query, session, local, memory, or emit_only) and whose values differ from current values produce changes.
is_popstate
boolean
default:"false"
When true, the URL is not updated. Used internally by the popstate event handler to restore state from history without creating a new history entry.

state.init()

Fire all registered listeners with the current URL data. Call this once after registering listeners to hydrate the initial page state from the URL:
state.setup({ query: ['tab', 'search'] })

state.on('tab search', render)

// Hydrate from current URL on page load
state.init()

state.clear()

Reset all in-memory state and remove all registered listeners. Useful in tests or when performing a full application reset:
state.clear()

Browser storage layout

All session and local state is stored under a single key $state as a JSON object in the respective Storage instance:
// sessionStorage['$state'] or localStorage['$state']:
{ "user": "ada", "preferences": { "compact": true } }
This keeps the browser storage namespace clean — only one key per storage type regardless of how many state keys you define.
When autolink: true is set, Nuestate intercepts clicks on <a href> elements whose pathname matches the configured route pattern. Modifier keys (Meta, Ctrl) are respected — modified clicks fall through to the browser’s default behavior.
<!-- With route: '/app/:section' and autolink: true -->
<a href="/app/users">Users</a>      <!-- SPA navigation -->
<a href="/app/settings">Settings</a> <!-- SPA navigation -->
<a href="https://example.com">External</a> <!-- Normal navigation -->
The intercepted URL is parsed with getPathData and getQueryData, the resulting state is applied via state.set(), and the browser history entry is pushed.

Complete example

import { state } from 'nuestate'

// 1. Configure
state.setup({
  route: '/shop/:category',
  query: ['search', 'sort', 'page'],
  local: ['theme'],
  autolink: true
})

// 2. React to changes
state.on('search sort page', async ({ search, sort, page }) => {
  const items = await fetchProducts({ search, sort, page })
  renderProducts(items)
})

state.on('theme', ({ theme }) => {
  document.documentElement.dataset.theme = theme
})

// 3. Hydrate from URL on load
state.init()

// 4. Update state from user interactions
document.querySelector('#search').addEventListener('input', e => {
  state.search = e.target.value  // URL updates, listener fires
  state.page = 1                  // reset pagination
})

document.querySelector('#theme-toggle').addEventListener('click', () => {
  state.theme = state.theme === 'dark' ? 'light' : 'dark'
})

Build docs developers (and LLMs) love