use-storage-state
Version:
React hook that you can wire with any Storage compatible API like `localStorage`, `sessionStorage`, or a custom one.
195 lines (130 loc) • 6.69 kB
Markdown
# `use-storage-state`
> React hook for any [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) compatible API like `localStorage`, `sessionStorage`, or a custom one.
[](https://bundlephobia.com/result?p=use-storage-state)
[](https://github.com/astoilkov/use-storage-state/actions/workflows/main.yml)
## Install
```bash
npm install use-storage-state
```
## Why
- SSR support.
- Production ready. [Used broadly across Twitch web properties](https://github.com/astoilkov/use-storage-state/issues/1#issuecomment-2344336086).
- Works with React 19 and React 18 concurrent rendering.
- Handles the `Window` [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event and updates changes across browser tabs, windows, and iframe's. Disable with `sync: false`.
- I also actively maintain [`use-local-storage-state`](https://github.com/astoilkov/use-local-storage-state) (400k downloads per month) for the past 4 years.
- Aiming for high-quality with [my open-source principles](https://astoilkov.com/my-open-source-principles).
## Usage
```typescript
import useStorageState from 'use-storage-state'
export default function Todos() {
const [todos, setTodos] = useStorageState('todos', {
defaultValue: ['buy avocado', 'do 50 push-ups']
})
}
```
<details>
<summary>Todo list example</summary>
<p></p>
```tsx
import React, { useState } from 'react'
import useStorageState from 'use-storage-state'
export default function Todos() {
const [todos, setTodos] = useStorageState('todos', {
defaultValue: ['buy avocado']
})
const [query, setQuery] = useState('')
function onClick() {
setQuery('')
setTodos([...todos, query])
}
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button onClick={onClick}>Create</button>
{todos.map(todo => (
<div>{todo}</div>
))}
</>
)
}
```
</details>
<details>
<summary id="remove-item">Removing the data from <code>Storage</code> and resetting to the default</summary>
<p></p>
The `removeItem()` method will reset the value to its default and will remove the key from the `Storage`. It returns to the same state as when the hook was initially created.
```tsx
import useStorageState from 'use-storage-state'
export default function Todos() {
const [todos, setTodos, removeItem] = useStorageState('todos', {
defaultValue: ['buy avocado']
})
function onClick() {
removeItem()
}
}
```
</details>
<details>
<summary>Why my component renders twice?</summary>
<p></p>
If you are hydrating your component (for example, if you are using Next.js), your component might re-render twice. This is behavior specific to React and not to this library. It's caused by the `useSyncExternalStore()` hook. There is no workaround.
If you want to know if you are currently rendering the server value you can use this helper function:
```ts
function useIsServerRender() {
return useSyncExternalStore(() => {
return () => {}
}, () => false, () => true)
}
```
</details>
## API
#### `useStorageState(key: string, options?: StorageStateOptions)`
Returns `[value, setValue, removeItem]` when called. The first two values are the same as `useState()`. The third value calls `Storage.removeItem()` and resets the hook to it's default state.
#### `key`
Type: `string`
The key used when calling `storage.setItem(key)` and `storage.getItem(key)`.
⚠️ Be careful with name conflicts as it is possible to access a property which is already in your storage that was created from another place in the codebase or in an old version of the application.
#### `options.defaultValue`
Type: `any`
Default: `undefined`
The default value. You can think of it as the same as `useState(defaultValue)`.
#### `options.storage`
Type: `"local" | "session" | Storage | undefined`
Default: `"local"`
You can set `localStorage`, `sessionStorage`, or other any [`Storage`](https://developer.mozilla.org/en-US/docs/Web/API/Storage) compatible class.
_Note:_ Prefer to use the `"local"` and `"session"` literals instead of `localStorage` or `sessionStorage` objects directly, as both can throw an error when accessed if user has configured the browser to not store any site data.
```ts
const [multiplier, setMultiplier] = useStorageState('multiplier', {
storage: "session" // default is "local"
})
```
#### `options.memoryFallback`
Type: `boolean`
Default: `true`
If you pass `undefined` to the `storage` option or `localStorage` or `sessionStorage` throw an error when accessed (possible when the browser is configured to not store any site data on device), the library uses a memory storage fallback to avoid your app from breaking completely. You can disable this behavior by setting this option to `false`.
#### `options.sync`
Type: `boolean`
Default: `true`
Setting to `false` doesn't subscribe to the [Window storage event](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event). If you set to `false`, updates won't be synchronized across tabs, windows and iframes.
#### `options.storeDefault`
Type: `boolean`
Default: `false`
Setting to `true` calls `storage.setItem()` for the default value so the default value is persisted in `Storage` after the first render of the hook.
#### `options.serializer`
Type: `{ stringify, parse }`
Default: `JSON`
JSON does not serialize `Date`, `Regex`, or `BigInt` data. You can pass in [superjson](https://github.com/blitz-js/superjson) or other `JSON`-compatible serialization library for more advanced serialization.
#### `memoryStorage`
The library exports a `memoryStorage` object that's used when the `memoryFallback` option is set to `true` (the default).
```ts
import { memoryStorage } from 'use-storage-state'
memoryStorage.getItem(key)
memoryStorage.setItem(key, value)
memoryStorage.removeItem(key)
```
## Related
- [`use-local-storage-state`](https://github.com/astoilkov/use-local-storage-state) — Similar to this hook but for `localStorage` only.
- [`use-session-storage-state`](https://github.com/astoilkov/use-session-storage-state) — Similar to this hook but for `sessionStorage` only.
- [`use-db`](https://github.com/astoilkov/use-db) — Similar to this hook but for `IndexedDB`.
- [`local-db-storage`](https://github.com/astoilkov/local-db-storage) — Tiny wrapper around `IndexedDB` that mimics `localStorage` API.