react-advanced-state-hook
v0.4.1
Published
A React hook that extends useState with persistence, debouncing, and advanced cross-component/tab sync.
Downloads
277
Maintainers
Readme
React Advanced State Hook
A powerful React hook that extends useState with persistence, debouncing, and
advanced cross-component/cross-tab state synchronization.
This hook is designed to be a lightweight, flexible, and robust solution for managing complex state in React applications.
Features
Centralized Configuration: Define your state's schema in one central place.
Component-Local State: Behaves just like
useStateby default.Persistent State: Easily persist state to
localStorageorsessionStorage.Debouncing: Debounce persistence and notifications to prevent storming.
Immediate UI Updates: UI updates are immediate; debouncing only applies to syncing.
Cross-Component Sync: Share state between components in the same tab (like Zustand).
Cross-Tab Sync: Share state between multiple browser tabs.
Hybrid Sync: Share state across components and tabs simultaneously.
Flexible Scoping:
Scope persistent state by URL parameters (e.g.,
?appId=123).Scope persistent state by URL path (e.g.,
/users/456/).Add a custom prefix for all storage keys.
SSR Safe: Works correctly in Server-Side Rendering (SSR) environments.
Installation
npm install react-advanced-state-hookQuick Start
Wrap your application (or the part that needs shared state) with the
AdvancedStateProvider. Define your persistent state schema using the
defaults prop.
// In your index.js or App.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { AdvancedStateProvider } from 'react-advanced-state-hook'
// Define your app's shared, persistent state schema
const appStateDefaults = [
{
key: 'username',
initial: 'Guest',
persist: 'local',
scopeByUrlParam: 'userId' // Scope this key by ?userId=...
},
{
key: 'theme',
initial: 'light',
persist: 'local' // This key is global, not scoped
}
]
ReactDOM.render(
<React.StrictMode>
<AdvancedStateProvider prefix='myApp' defaults={appStateDefaults}>
<App />
</AdvancedStateProvider>
</React.StrictMode>,
document.getElementById('root')
)Now you can use the hook anywhere in your app. It will automatically inherit
the persist and scope settings from the provider.
import { useAdvancedState } from 'react-advanced-state-hook'
function UserProfile() {
// This hook call is now very clean.
// It automatically gets its 'initial', 'persist', and 'scopeByUrlParam'
// settings from the 'defaults' prop on the provider.
const [username, setUsername] = useAdvancedState('username', {
// We only pass options here to *override* a default
// or set a transient option like 'notify'.
notify: 'cross-component-and-tab',
debounce: 300
})
return (
<div>
<label>Username:</label>
<input value={username} onChange={e => setUsername(e.target.value)} />
</div>
)
}API
useAdvancedState(key, options)
This is the main hook you will use. It takes a required key and an optional
options object.
key(string) Required. The unique key for this piece of state (e.g.,'username').options(object) An optional object to configure the hook's advanced features.
Options
initial(any) The initial value to use if no value is found in storage. This value is eagerly written to storage on mount if storage is empty.debounce(number) Debounce delay in milliseconds. Applies only to persistence and notifications, not the local UI update.persist(string) Specifies where to persist the state.'local'forlocalStorage.'session'forsessionStorage.- If not set, state is in-memory only.
notify(string) Defines the synchronization strategy.'cross-component': Notifies other components in the same tab (requiresAdvancedStateProvider).'cross-tab': Notifies other tabs (requirespersist).'cross-component-and-tab': Does both (requiresAdvancedStateProviderandpersist).- If not set, no one is notified. Behaves like
useState
scopeByUrlParam(string) Scopes storage key by a URL parameter. E.g.,'appId'uses the value of?appId=....scopeByUrlPath(string) Scopes storage key by URL path segments. Uses string replacement for placeholders$1,$2, etc. E.g.,user_$1with path/users/123becomesuser_123.
<AdvancedStateProvider>
This provider component is required if you use the
notify: 'cross-component' or notify: 'cross-component-and-tab' options.
It's also used to set a custom prefix for all your storage keys.
Props
prefix(string) A custom prefix for all storage keys. Defaults to'advState'.defaults(Array<object>) Recommended. An array of default configuration objects for your persistent state. Each object can containkey,initial,notify,persist,debounce,scopeByUrlParam, andscopeByUrlPath.
How is this different from Zustand?
Zustand is a fantastic, simple global state manager. This hook solves a similar problem but with a different philosophy and some unique advantages.
Atomic vs. Single Store: Zustand creates a single, global store that holds all your state in one object (like Redux). This hook is an "atomic" state manager (like Recoil or Jotai), where each piece of state is managed independently by its key. Using this hook for all your shared state is a perfectly valid and performant approach.
API: Zustand uses a selector-based API (
useStore(state => state.user)). This hook mimicsuseState([value, setValue]), making it feel like a "supercharged" version of React's built-in hook.Persistence Performance: When Zustand's
persistmiddleware saves, it stringifies your entire state object. This hook only stringifies the single value that changed. For large states, this can be a significant performance advantage on writes.Built-in Features: This hook was designed from the ground up with persistence, robust cross-tab synchronization, and URL-based scoping as core, first-class features, not just middleware.
Use this hook when you want to easily upgrade individual pieces of state with persistence and cross-tab sync. Use Zustand when you want a more traditional, single-store global state solution.
Storage Key Format
The hook generates a clean, readable key for storage:
<prefix>:<scopeValue>:<key>
Examples:
With
prefix: 'myApp',key: 'username':myApp:usernameWith
prefix: 'myApp',key: 'docTitle',scopeByUrlParam: 'docId'and URL.../?docId=123:myApp:123:docTitleWith
prefix: 'wiki',key: 'content',scopeByUrlPath: 'page_$2'and URL.../user/abc/page/456:wiki:page_abc:content
Best Practices & Caveats
Use
defaultsfor Persistent State. Define all your persistent, shared state in thedefaultsprop on theAdvancedStateProvider. This centralizes your app's state schema, ensures correct initialization, and keeps your hook calls clean.Be Consistent. If you don't use the
defaultsprop, all components sharing the samekeyshould use the exact samepersistandscopeoptions.Provider Placement. Place the
AdvancedStateProviderat the highest level possible, wrapping your entire application.initialValue Precedence: The initial value indefaultsis used to pre-populate storage. If a component mounts and storage is still empty (e.g., for a new URL scope), its owninitialvalue will be used for that scope.Scoping is for Storage: The scopeByUrlParam and scopeByUrlPath options only affect persistence. They have no effect if
persistis not set.
How to Test (for development)
You can test this library locally by using the example/App.js and
example/index.js files.
Clone the repository.
Create a test project: In a separate directory, create a new React app (e.g.,
npx create-react-app test-app).Link your library:
In your
react-advanced-state-hooklibrary folder, run:npm linkIn your
test-appfolder, run:npm link react-advanced-state-hook
Replace test app files:
Copy the
example/App.jsfile from the library into yourtest-app/src/folder.Copy the
example/index.jsfile from the library into yourtest-app/src/folder.
Run the test app:
npm startYou now have a running application where you can test all the features, including cross-tab sync and URL scoping.
