Skip to content

useStorage

Category
Export Size
1.14 kB
Last Changed
5 days ago
Related

Create a controllable state that can be used to access & modify LocalStorage or SessionStorage.

Uses localStorage by default, other storage sources be specified via third argument.

Demo

Usage

tsx
import { 
useStorage
} from '@reause/core'
const [
state
,
setState
] =
useStorage
('my-store', {
hello
: 'hi',
greeting
: 'Hello' })
const [
flag
,
setFlag
] =
useStorage
('my-flag', true)
const [
id
,
setId
] =
useStorage
('my-id', 'some-string-id',
sessionStorage
)
setState
(null) // delete data from storage

Merge Defaults

By default, useStorage will use the value from storage if it is present and ignores the default value. Be aware that when you are adding more properties to the default value, the key might be undefined if client's storage does not have that key.

tsx
localStorage
.
setItem
('my-store', '{"hello": "hello"}')
const [
state
,
setState
] =
useStorage
('my-store', {
hello
: 'hi',
greeting
: 'hello' },
localStorage
)
console
.
log
(
state
.
greeting
) // undefined, since the value is not presented in storage

To solve that, you can enable mergeDefaults option.

tsx
localStorage
.
setItem
('my-store', '{"hello": "nihao"}')
const [
state
,
setState
] =
useStorage
(
'my-store', {
hello
: 'hi',
greeting
: 'hello' },
localStorage
,
{
mergeDefaults
: true }, // <--
)
console
.
log
(
state
.
hello
) // 'nihao', from storage
console
.
log
(
state
.
greeting
) // 'hello', from merged default value

When setting it to true, it will perform a shallow merge for objects. You can pass a function to perform custom merge (e.g. deep merge), for example:

tsx
const [
state
,
setState
] =
useStorage
(
'my-store', {
hello
: 'hi',
greeting
: 'hello' },
localStorage
,
{
mergeDefaults
: (
storageValue
,
defaults
) => deepMerge(
defaults
,
storageValue
) }, // <--
)

Custom Serialization

By default, useStorage will smartly use the corresponding serializer based on the data type of provided default value. For example, JSON.stringify / JSON.parse will be used for objects, Number.toString / parseFloat for numbers, etc.

You can also provide your own serialization function to useStorage:

tsx
import { 
useStorage
} from '@reause/core'
useStorage
(
'key', {},
undefined
,
{
serializer
: {
read
: (
v
: any) =>
v
?
JSON
.
parse
(
v
) : null,
write
: (
v
: any) =>
JSON
.
stringify
(
v
),
}, }, )

Please note when you provide null as the default value, useStorage can't assume the data type from it. In this case, you can provide a custom serializer or reuse the built-in ones explicitly.

tsx
import { 
StorageSerializers
,
useStorage
} from '@reause/core'
const [
objectLike
,
setObjectLike
] =
useStorage
('key', null,
undefined
, {
serializer
:
StorageSerializers
.
object
})
setObjectLike
({
foo
: 'bar' })

Built-in Serializers

The following serializers are available via StorageSerializers:

TypeDescription
stringPlain string
numberNumber (via parseFloat)
booleanBoolean
objectJSON object/array
mapJavaScript Map
setJavaScript Set
dateJavaScript Date (via toISOString)
anyRaw string passthrough
tsx
import { 
StorageSerializers
,
useStorage
} from '@reause/core'
const [
myMap
,
setMyMap
] =
useStorage
('my-map', new
Map
(),
undefined
, {
serializer
:
StorageSerializers
.
map
,
})

Options

tsx
useStorage
('key', defaults, storage, {
// Sync across tabs via storage events (default: true)
listenToStorageChanges
: true,
// Write default value to storage if not present (default: true)
writeDefaults
: true,
// Custom error handler (default: console.error)
onError
:
e
=>
console
.
error
(
e
),
})

Reactive Key

The storage key can be derived from state — the data will be updated when the key changes between renders:

tsx
import { 
useStorage
} from '@reause/core'
import {
useState
} from 'react'
const [
userId
,
setUserId
] =
useState
('user-1')
const
userData
=
useStorage
(
`user-data-${
userId
}`,
{
name
: '' },
) // Changing the key will read from the new storage location
setUserId
('user-2')

Type Declarations

Toggle
ts
export interface StorageLike {
    getItem: (key: string) => string | null;
    setItem: (key: string, value: string) => void;
    removeItem: (key: string) => void;
}

export interface StorageEventLike {
    storageArea: StorageLike | null;
    key: StorageEvent['key'];
    oldValue: StorageEvent['oldValue'];
    newValue: StorageEvent['newValue'];
}

export interface UseStorageOptions<T> {
    window?: Window;
    listenToStorageChanges?: boolean;
    writeDefaults?: boolean;
    mergeDefaults?: boolean | ((storageValue: T, defaults: T) => T);
    serializer?: Serializer<T>;
    onError?: (error: unknown) => void;
}

export type UseStorageReturn<T> = [
    value: T | null,
    setValue: Dispatch<SetStateAction<T | null>>
]

export type SerializerType = 'boolean' | 'object' | 'number' | 'any' | 'string' | 'map' | 'set' | 'date'

interface Serializer<T> {
    read: (raw: string) => T;
    write: (value: T) => string;
}

export const customStorageEventName

export const StorageSerializers: Record<'boolean' | 'object' | 'number' | 'any' | 'string' | 'map' | 'set' | 'date', Serializer<any>>

export function guessSerializerType<T extends (string | number | boolean | object | null)>(rawInit: T): SerializerType

export function useStorage(key: string, defaults: string, storage?: StorageLike, options?: UseStorageOptions<string>): UseStorageReturn<string>

export function useStorage(key: string, defaults: boolean, storage?: StorageLike, options?: UseStorageOptions<boolean>): UseStorageReturn<boolean>

export function useStorage(key: string, defaults: number, storage?: StorageLike, options?: UseStorageOptions<number>): UseStorageReturn<number>

export function useStorage<T>(key: string, defaults: T | (() => T), storage?: StorageLike, options?: UseStorageOptions<T>): UseStorageReturn<T>

export function useStorage<T = unknown>(key: string, defaults: null, storage?: StorageLike, options?: UseStorageOptions<T>): UseStorageReturn<T>

Source

Source · Demo · VueUse

Contributors

hairyf

Changelog

v0.1.0 on
7d679 - fix(core): resolve useStorage audit findings (#649)
8fddc - chore!: remove all Vue-only Maybe* types and getter unions, adopt React Ref (#462)

Released under the MIT License. v0.1.8