use-local-storage-state
Advanced tools
Comparing version 13.0.0 to 14.0.0-0
@@ -1,4 +0,2 @@ | ||
import useLocalStorageState from './src/useLocalStorageState'; | ||
import createLocalStorageStateHook from './src/createLocalStorageStateHook'; | ||
export default useLocalStorageState; | ||
export { useLocalStorageState, createLocalStorageStateHook }; | ||
import createLocalStorageHook from './src/createLocalStorageHook'; | ||
export default createLocalStorageHook; |
@@ -10,10 +10,11 @@ /** | ||
*/ | ||
const data = {}; | ||
export default { | ||
data: new Map(), | ||
get(key, defaultValue) { | ||
var _a; | ||
try { | ||
return (_a = data[key]) !== null && _a !== void 0 ? _a : parseJSON(localStorage.getItem(key)); | ||
return this.data.has(key) | ||
? this.data.get(key) | ||
: parseJSON(localStorage.getItem(key)); | ||
} | ||
catch (_b) { | ||
catch (_a) { | ||
return defaultValue; | ||
@@ -25,12 +26,10 @@ } | ||
localStorage.setItem(key, JSON.stringify(value)); | ||
data[key] = undefined; | ||
return true; | ||
this.data.delete(key); | ||
} | ||
catch (_a) { | ||
data[key] = value; | ||
return false; | ||
this.data.set(key, value); | ||
} | ||
}, | ||
remove(key) { | ||
data[key] = undefined; | ||
this.data.delete(key); | ||
localStorage.removeItem(key); | ||
@@ -37,0 +36,0 @@ }, |
@@ -1,25 +0,78 @@ | ||
import { useEffect } from 'react'; | ||
import useLocalStorageStateBase from './useLocalStorageStateBase'; | ||
/** | ||
* Used to track usages of `useLocalStorageState()` with identical `key` values. If we encounter | ||
* duplicates we throw an error to the user telling them to use `createLocalStorageStateHook` | ||
* instead. | ||
*/ | ||
const initializedStorageKeys = new Set(); | ||
import storage from './storage'; | ||
import { unstable_batchedUpdates } from 'react-dom'; | ||
import { useEffect, useMemo, useReducer } from 'react'; | ||
// `activeHooks` holds all active hooks. we use the array to update all hooks with the same key — | ||
// calling `setValue` of one hook triggers an update for all other hooks with the same key | ||
const activeHooks = []; | ||
export default function useLocalStorageState(key, defaultValue) { | ||
const value = useLocalStorageStateBase(key, defaultValue); | ||
/** | ||
* Detects incorrect usage of the library and throws an error with a suggestion how to fix it. | ||
*/ | ||
// `id` changes every time a change in the `localStorage` occurs | ||
const [id, forceUpdate] = useReducer((number) => number + 1, 0); | ||
// initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26 | ||
// issues that were caused by incorrect initial and secondary implementations: | ||
// - https://github.com/astoilkov/use-local-storage-state/issues/30 | ||
// - https://github.com/astoilkov/use-local-storage-state/issues/33 | ||
if (defaultValue !== undefined && | ||
!storage.data.has(key) && | ||
localStorage.getItem(key) === null) { | ||
storage.set(key, defaultValue); | ||
} | ||
// - syncs change across tabs, windows, iframe's | ||
// - the `storage` event is called only in all tabs, windows, iframe's except the one that | ||
// triggered the change | ||
useEffect(() => { | ||
if (initializedStorageKeys.has(key)) { | ||
throw new Error(`When using the same key in multiple places use createLocalStorageStateHook('${key}'): ` + | ||
`https://github.com/astoilkov/use-local-storage-state#create-local-storage-state-hook`); | ||
} | ||
else { | ||
initializedStorageKeys.add(key); | ||
} | ||
return () => void initializedStorageKeys.delete(key); | ||
const onStorage = (e) => { | ||
if (e.storageArea === localStorage && e.key === key) { | ||
forceUpdate(); | ||
} | ||
}; | ||
window.addEventListener('storage', onStorage); | ||
return () => window.removeEventListener('storage', onStorage); | ||
// `key` never changes | ||
}, [key]); | ||
return value; | ||
// add this hook to the `activeHooks` array. see the `activeHooks` declaration above for a | ||
// more detailed explanation | ||
useEffect(() => { | ||
const entry = { key, forceUpdate }; | ||
activeHooks.push(entry); | ||
return () => { | ||
activeHooks.splice(activeHooks.indexOf(entry), 1); | ||
}; | ||
// both `key` and `forceUpdate` never change | ||
}, [key, forceUpdate]); | ||
return useMemo(() => [ | ||
storage.get(key, defaultValue), | ||
(newValue) => { | ||
const isCallable = (value) => typeof value === 'function'; | ||
const newUnwrappedValue = isCallable(newValue) | ||
? newValue(storage.get(key, defaultValue)) | ||
: newValue; | ||
storage.set(key, newUnwrappedValue); | ||
unstable_batchedUpdates(() => { | ||
for (const update of activeHooks) { | ||
if (update.key === key) { | ||
update.forceUpdate(); | ||
} | ||
} | ||
}); | ||
}, | ||
{ | ||
isPersistent: !storage.data.has(key), | ||
removeItem() { | ||
storage.remove(key); | ||
for (const update of activeHooks) { | ||
if (update.key === key) { | ||
update.forceUpdate(); | ||
} | ||
} | ||
}, | ||
}, | ||
], | ||
// disabling eslint warning for the following reasons: | ||
// - `id` is needed because when it changes that means the data in `localStorage` has | ||
// changed and we need to update the returned value. However, the eslint rule wants us to | ||
// remove the `id` from the dependencies array. | ||
// - `key` never changes so we can skip it and reduce package size | ||
// - `defaultValue` never changes so we can skip it and reduce package size | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[id]); | ||
} |
import storage from './storage'; | ||
import { useEffect, useMemo, useRef, useState } from 'react'; | ||
import unwrapValue from './unwrapValue'; | ||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||
export default function useLocalStorageStateBase(key, defaultValue) { | ||
const defaultValueForKey = useMemo(() => { | ||
const isCallable = (value) => typeof value === 'function'; | ||
return isCallable(defaultValue) ? defaultValue() : defaultValue; | ||
// disabling "exhaustive-deps" because we don't want to change the default state when the | ||
// `defaultValue` is changed | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [key]); | ||
const unwrappedDefaultValue = useMemo(() => unwrapValue(defaultValue), | ||
// disabling "exhaustive-deps" because we don't want to change the default state when the | ||
// `defaultValue` is changed | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[key]); | ||
const defaultState = useMemo(() => { | ||
return { | ||
value: storage.get(key, defaultValueForKey), | ||
value: storage.get(key, unwrappedDefaultValue), | ||
isPersistent: (() => { | ||
@@ -36,22 +35,13 @@ /** | ||
}; | ||
}, [key, defaultValueForKey]); | ||
}, [key, unwrappedDefaultValue]); | ||
const [{ value, isPersistent }, setState] = useState(defaultState); | ||
const updateValue = useMemo(() => { | ||
return (newValue) => { | ||
const isCallable = (value) => typeof value === 'function'; | ||
if (isCallable(newValue)) { | ||
setState((state) => ({ | ||
value: newValue(state.value), | ||
isPersistent: storage.set(key, newValue(state.value)), | ||
})); | ||
} | ||
else { | ||
setState({ | ||
value: newValue, | ||
isPersistent: storage.set(key, newValue), | ||
}); | ||
} | ||
const updateValue = useCallback((newValue) => setState((state) => { | ||
const isCallable = (value) => typeof value === 'function'; | ||
const newUnwrappedValue = isCallable(newValue) ? newValue(state.value) : newValue; | ||
return { | ||
value: newUnwrappedValue, | ||
isPersistent: storage.set(key, newUnwrappedValue), | ||
}; | ||
}, [key]); | ||
// syncs changes across tabs and iframe's | ||
}), [key]); | ||
// syncs change across tabs and iframe's | ||
useEffect(() => { | ||
@@ -61,3 +51,3 @@ const onStorage = (e) => { | ||
setState({ | ||
value: storage.get(key, defaultValueForKey), | ||
value: storage.get(key, unwrappedDefaultValue), | ||
isPersistent: true, | ||
@@ -69,22 +59,20 @@ }); | ||
return () => window.removeEventListener('storage', onStorage); | ||
}, [key, defaultValueForKey]); | ||
}, [key, unwrappedDefaultValue]); | ||
const isFirstRender = useRef(true); | ||
useEffect(() => { | ||
// https://github.com/astoilkov/use-local-storage-state/issues/30 | ||
if (isFirstRender.current && defaultState.value === undefined) { | ||
return; | ||
} | ||
// https://github.com/astoilkov/use-local-storage-state/issues/33 | ||
if (localStorage.getItem(key) === null) { | ||
// set the `defaultValue` in the localStorage on initial render: | ||
// https://github.com/astoilkov/use-local-storage-state/issues/26 | ||
// initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26 | ||
// issues that were caused by incorrect initial and secondary implementations: | ||
// - https://github.com/astoilkov/use-local-storage-state/issues/30 | ||
// - https://github.com/astoilkov/use-local-storage-state/issues/33 | ||
if (defaultState.value !== undefined && localStorage.getItem(key) === null) { | ||
storage.set(key, defaultState.value); | ||
} | ||
// call `setState(defaultState)` below only when the `key` property changes — not on first | ||
// render because this will cause a second unnecessary render | ||
if (isFirstRender.current) { | ||
isFirstRender.current = false; | ||
return; | ||
} | ||
// update the state when the `key` property changes (not on first render because this will | ||
// cause a second unnecessary render) | ||
setState(defaultState); | ||
else { | ||
setState(defaultState); | ||
} | ||
}, [key, defaultState]); | ||
@@ -99,3 +87,3 @@ return useMemo(() => [ | ||
setState((state) => ({ | ||
value: defaultValueForKey, | ||
value: unwrappedDefaultValue, | ||
isPersistent: state.isPersistent, | ||
@@ -105,3 +93,3 @@ })); | ||
}, | ||
], [value, updateValue, isPersistent, key, defaultValueForKey]); | ||
], [value, updateValue, isPersistent, key, unwrappedDefaultValue]); | ||
} |
@@ -1,4 +0,2 @@ | ||
import useLocalStorageState from './src/useLocalStorageState'; | ||
import createLocalStorageStateHook from './src/createLocalStorageStateHook'; | ||
export default useLocalStorageState; | ||
export { useLocalStorageState, createLocalStorageStateHook }; | ||
import createLocalStorageHook from './src/createLocalStorageHook'; | ||
export default createLocalStorageHook; |
11
index.js
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createLocalStorageStateHook = exports.useLocalStorageState = void 0; | ||
const useLocalStorageState_1 = require("./src/useLocalStorageState"); | ||
exports.useLocalStorageState = useLocalStorageState_1.default; | ||
const createLocalStorageStateHook_1 = require("./src/createLocalStorageStateHook"); | ||
exports.createLocalStorageStateHook = createLocalStorageStateHook_1.default; | ||
exports.default = useLocalStorageState_1.default; | ||
const createLocalStorageHook_1 = __importDefault(require("./src/createLocalStorageHook")); | ||
exports.default = createLocalStorageHook_1.default; |
{ | ||
"name": "use-local-storage-state", | ||
"version": "13.0.0", | ||
"version": "14.0.0-0", | ||
"description": "React hook that persist data in localStorage", | ||
@@ -8,2 +8,3 @@ "license": "MIT", | ||
"funding": "https://github.com/sponsors/astoilkov", | ||
"homepage": "https://github.com/astoilkov/use-local-storage-state", | ||
"author": { | ||
@@ -32,3 +33,3 @@ "name": "Antonio Stoilkov", | ||
"lint": "eslint --cache --format=pretty --ext=.ts ./", | ||
"test": "yarn run build && yarn run lint && [[ -z $CI ]] && jest --coverage --coverageReporters=text || jest --coverage", | ||
"test": "yarn run build && yarn run lint && if [[ -z $CI ]]; then jest --coverage --coverageReporters=text; else jest --coverage; fi", | ||
"release": "yarn run build && np", | ||
@@ -38,3 +39,3 @@ "prettier": "prettier --write --config .prettierrc.yaml {*.ts,*.json}" | ||
"engines": { | ||
"node": ">=10" | ||
"node": ">=12" | ||
}, | ||
@@ -53,18 +54,18 @@ "files": [ | ||
"devDependencies": { | ||
"@size-limit/preset-small-lib": "^4.9.2", | ||
"@testing-library/react-hooks": "^4.0.0", | ||
"@types/jest": "^26.0.14", | ||
"@types/react": "^17.0.0", | ||
"@size-limit/preset-small-lib": "^7.0.5", | ||
"@testing-library/react-hooks": "^7.0.2", | ||
"@types/jest": "^27.4.0", | ||
"@types/react": "^17.0.38", | ||
"@types/react-dom": "^17.0.0", | ||
"@typescript-eslint/eslint-plugin": "^4.11.0", | ||
"@typescript-eslint/parser": "^4.11.0", | ||
"confusing-browser-globals": "^1.0.10", | ||
"eslint": "^7.16.0", | ||
"@typescript-eslint/eslint-plugin": "^5.10.0", | ||
"@typescript-eslint/parser": "^5.10.0", | ||
"confusing-browser-globals": "^1.0.11", | ||
"eslint": "^8.7.0", | ||
"eslint-config-strictest": "^0.3.1", | ||
"eslint-formatter-pretty": "^4.0.0", | ||
"eslint-plugin-promise": "^4.2.1", | ||
"eslint-plugin-react": "^7.21.5", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"eslint-plugin-react": "^7.28.0", | ||
"eslint-plugin-react-hooks": "^4.2.0", | ||
"eslint-plugin-unicorn": "^24.0.0", | ||
"jest": "^26.6.3", | ||
"eslint-plugin-unicorn": "^40.1.0", | ||
"jest": "^27.4.7", | ||
"np": "^7.2.0", | ||
@@ -75,5 +76,5 @@ "prettier": "^2.0.5", | ||
"react-test-renderer": "^17.0.1", | ||
"size-limit": "^4.9.2", | ||
"ts-jest": "^26.4.4", | ||
"typescript": "^4.1.3" | ||
"size-limit": "^7.0.5", | ||
"ts-jest": "^27.1.3", | ||
"typescript": "^4.5.4" | ||
}, | ||
@@ -86,18 +87,4 @@ "size-limit": [ | ||
"gzip": false | ||
}, | ||
{ | ||
"name": "import { useLocalStorageState }", | ||
"path": "es/index.js", | ||
"import": "{ useLocalStorageState }", | ||
"limit": "1.6 KB", | ||
"gzip": false | ||
}, | ||
{ | ||
"name": "import { createLocalStorageState }", | ||
"path": "es/index.js", | ||
"import": "{ createLocalStorageStateHook }", | ||
"limit": "1.6 KB", | ||
"gzip": false | ||
} | ||
] | ||
} |
163
readme.md
# `use-local-storage-state` | ||
> React hook that persist data in local storage | ||
> React hook that persist data in `localStorage` | ||
[![Downloads](https://img.shields.io/npm/dm/use-local-storage-state)](https://www.npmjs.com/package/use-local-storage-state) | ||
[![Minified Size](https://badgen.net/bundlephobia/min/use-local-storage-state)](https://bundlephobia.com/result?p=use-local-storage-state) | ||
[![Test Coverage](https://img.shields.io/codeclimate/coverage/astoilkov/use-local-storage-state)](https://codeclimate.com/github/astoilkov/use-local-storage-state/test_coverage) | ||
[![Build Status](https://www.travis-ci.com/astoilkov/use-local-storage-state.svg?branch=master)](https://travis-ci.org/astoilkov/use-local-storage-state) | ||
[![Test Coverage](https://img.shields.io/codeclimate/coverage/astoilkov/use-local-storage-state)](https://codeclimate.com/github/astoilkov/use-local-storage-state/test_coverage) | ||
[![Minified Size](https://img.shields.io/npm/dm/use-local-storage-state)](https://www.npmjs.com/package/use-local-storage-state) | ||
[![Minified Size](https://badgen.net/bundlephobia/min/use-local-storage-state)](https://bundlephobia.com/result?p=use-local-storage-state) | ||
@@ -18,23 +18,26 @@ ## Install | ||
Few other libraries also try to abstract the usage of localStorage into a hook. Here are the reasons why you would consider this one: | ||
- Actively maintained for the past 2 years — see [contributions](https://github.com/astoilkov/use-local-storage-state/graphs/contributors) page. | ||
- SSR support with handling of [hydration mismatches](https://github.com/astoilkov/use-local-storage-state/issues/23). | ||
- In-memory fallback when `localStorage` throws an error and can't store the data. Provides a `isPersistent` API to let you notify the user their data isn't currently being stored. | ||
- 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. | ||
- Aiming for high-quality with [my open-source principles](https://astoilkov.com/my-open-source-principles). | ||
- Uses `JSON.parse()` and `JSON.stringify()` to support non string values | ||
- SSR support | ||
- 100% test coverage. No `istanbul ignore` | ||
- Handles edge cases – [example](#is-persistent-example) | ||
- Subscribes to the Window [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event which tracks changes across browser tabs and iframe's | ||
- Aiming for high-quality with [my open-source principles](https://astoilkov.com/my-open-source-principles) | ||
## Usage | ||
```typescript | ||
import useLocalStorageState from 'use-local-storage-state' | ||
import createLocalStorageHook from 'use-local-storage-state' | ||
const [todos, setTodos] = useLocalStorageState('todos', [ | ||
'buy milk', | ||
'do 50 push-ups' | ||
]) | ||
const useTodos = createLocalStorageHook('todos', { | ||
ssr: true, | ||
defaultValue: ['buy avocado', 'do 50 push-ups'] | ||
}) | ||
export default function Todos() { | ||
const [todos, setTodos] = useTodos() | ||
} | ||
``` | ||
### Todo list example | ||
<details> | ||
<summary>Todo list example + CodeSandbox link</summary> | ||
<p></p> | ||
@@ -45,7 +48,11 @@ You can experiment with the example [here](https://codesandbox.io/s/todos-example-q48ch?file=/src/App.tsx). | ||
import React, { useState } from 'react' | ||
import useLocalStorageState from 'use-local-storage-state' | ||
import createLocalStorateHook from 'use-local-storage-state' | ||
const useTodos = createLocalStorateHook('todos', { | ||
defaultValue: ['buy avocado'] | ||
}) | ||
export default function Todos() { | ||
const [todos, setTodos] = useTodos() | ||
const [query, setQuery] = useState('') | ||
const [todos, setTodos] = useLocalStorageState('todos', ['buy milk']) | ||
@@ -61,3 +68,5 @@ function onClick() { | ||
<button onClick={onClick}>Create</button> | ||
{todos.map(todo => (<div>{todo}</div>))} | ||
{todos.map(todo => ( | ||
<div>{todo}</div> | ||
))} | ||
</> | ||
@@ -69,23 +78,29 @@ ) | ||
<div id="is-persistent-example"></div> | ||
</details> | ||
### Reseting to defaults | ||
<details> | ||
<summary>SSR support</summary> | ||
<p></p> | ||
The `removeItem()` method will reset the value to its default and will remove the key from the `localStorage`. It returns to the same state as when the hook was initially created. | ||
SSR supports includes handling of hydration mismatches. This prevents the following error: `Warning: Expected server HTML to contain a matching ...`. This is the only library I'm aware of that handles this case. For more, see [discussion here](https://github.com/astoilkov/use-local-storage-state/issues/23). | ||
```tsx | ||
import useLocalStorageState from 'use-local-storage-state' | ||
import createLocalStorageHook from 'use-local-storage-state' | ||
const [todos, setTodos, { removeItem }] = useLocalStorageState('todos', [ | ||
'buy milk', | ||
'do 50 push-ups' | ||
]) | ||
const useTodos = createLocalStorageHook('todos', { | ||
ssr: true, | ||
defaultValue: ['buy avocado', 'do 50 push-ups'] | ||
}) | ||
function onClick() { | ||
removeItem() | ||
export default function Todos() { | ||
const [todos, setTodos] = useTodos() | ||
} | ||
``` | ||
### Handling edge cases with `isPersistent` | ||
</details> | ||
<details> | ||
<summary id="is-persistent">Notify the user when <code>localStorage</code> isn't saving the data using the <code>`isPersistent`</code> property</summary> | ||
<p></p> | ||
There are a few cases when `localStorage` [isn't available](https://github.com/astoilkov/use-local-storage-state/blob/7db8872397eae8b9d2421f068283286847f326ac/index.ts#L3-L11). The `isPersistent` property tells you if the data is persisted in `localStorage` or in-memory. Useful when you want to notify the user that their data won't be persisted. | ||
@@ -97,4 +112,8 @@ | ||
const useTodos = createLocalStorateHook('todos', { | ||
defaultValue: ['buy avocado'] | ||
}) | ||
export default function Todos() { | ||
const [todos, setTodos, { isPersistent }] = useLocalStorageState('todos', ['buy milk']) | ||
const [todos, setTodos, { isPersistent }] = useTodos() | ||
@@ -111,67 +130,59 @@ return ( | ||
## API | ||
</details> | ||
### useLocalStorageState(key, defaultValue?) | ||
<details> | ||
<summary id="remove-item">Removing the data from <code>localStorage</code> and resetting to the default</summary> | ||
<p></p> | ||
Returns `[value, setValue, { removeItem, isPersistent }]`. The first two values are the same as `useState()`. The third value contains extra properties specific to `localStorage`: | ||
- `removeItem()` — [example](#reseting-to-defaults) | ||
- `isPersistent()` — [example](#handling-edge-cases-with-ispersistent) | ||
The `removeItem()` method will reset the value to its default and will remove the key from the `localStorage`. It returns to the same state as when the hook was initially created. | ||
#### key | ||
```tsx | ||
import useLocalStorageState from 'use-local-storage-state' | ||
Type: `string` | ||
const useTodos = createLocalStorateHook('todos', { | ||
defaultValue: ['buy avocado'] | ||
}) | ||
The key used when calling `localStorage.setItem(key)`and `localStorage.getItem(key)`. | ||
export default function Todos() { | ||
const [todos, setTodos, { removeItem }] = useTodos() | ||
⚠️ Be careful with name conflicts as it is possible to access a property which is already in `localStorage` that was created from another place in the codebase or in an old version of the application. | ||
function onClick() { | ||
removeItem() | ||
} | ||
} | ||
``` | ||
#### defaultValue | ||
</details> | ||
Type: `any` | ||
Default: `undefined` | ||
## API | ||
The initial value of the data. The same as `useState(defaultValue)` property. | ||
### `createLocalStorageHook(key, options?)` | ||
<div id="create-local-storage-state-hook"></div> | ||
Returns a React hook that returns `[value, setValue, { removeItem, isPersistent }]` when called. The first two values are the same as `useState()`. The third value contains two extra properties: | ||
- `removeItem()` — calls `localStorage.removeItem(key)` and resets the hook to it's default state | ||
- `isPersistent` — `boolean` property that returns `false` is `localStorage` is throwing an error and the data is stored only in-memory | ||
### createLocalStorageStateHook(key, defaultValue?) | ||
### `key` | ||
If you want to have the same data in multiple components in your code use `createLocalStorageStateHook()` instead of `useLocalStorageState()`. This avoids: | ||
- maintenance issues with duplicate code that should always be in sync | ||
- conflicts with different default values | ||
- `key` parameter misspellings | ||
Type: `string` | ||
```typescript | ||
import { createLocalStorageStateHook } from 'use-local-storage-state' | ||
The key used when calling `localStorage.setItem(key)` and `localStorage.getItem(key)`. | ||
// Todos.tsx | ||
const useTodos = createLocalStorageStateHook('todos', [ | ||
'buy milk', | ||
'do 50 push-ups' | ||
]) | ||
function Todos() { | ||
const [todos, setTodos] = useTodos() | ||
} | ||
⚠️ Be careful with name conflicts as it is possible to access a property which is already in `localStorage` that was created from another place in the codebase or in an old version of the application. | ||
// Popup.tsx | ||
import useTodos from './useTodos' | ||
function Popup() { | ||
const [todos, setTodos] = useTodos() | ||
} | ||
``` | ||
### `options.defaultValue` | ||
#### key | ||
Type: `any` | ||
Type: `string` | ||
Default: `undefined` | ||
The key used when calling `localStorage.setItem(key)`and `localStorage.getItem(key)`. | ||
The default value. You can think of it as the same as `useState(defaultValue)`. | ||
⚠️ Be careful with name conflicts as it is possible to access a property which is already in `localStorage` that was created from another place in the codebase or in an old version of the application. | ||
### `options.ssr` | ||
#### defaultValue | ||
Type: `boolean` | ||
Type: `any` | ||
Default: `undefined` | ||
Default: `false` | ||
The initial value of the data. The same as `useState(defaultValue)` property. | ||
Enables SSR support and handles hydration mismatches. Not enabling this can cause the following error: `Warning: Expected server HTML to contain a matching ...`. This is the only library I'm aware of that handles this case. For more, see [discussion here](https://github.com/astoilkov/use-local-storage-state/issues/23). | ||
@@ -178,0 +189,0 @@ ## Alternatives |
declare const _default: { | ||
data: Map<string, unknown>; | ||
get<T>(key: string, defaultValue: T): T | undefined; | ||
set<T_1>(key: string, value: T_1): boolean; | ||
set<T_1>(key: string, value: T_1): void; | ||
remove(key: string): void; | ||
}; | ||
/** | ||
* Abstraction for localStorage that uses an in-memory fallback when localStorage throws an error. | ||
* Reasons for throwing an error: | ||
* - maximum quota is exceeded | ||
* - under Mobile Safari (since iOS 5) when the user enters private mode `localStorage.setItem()` | ||
* will throw | ||
* - trying to access localStorage object when cookies are disabled in Safari throws | ||
* "SecurityError: The operation is insecure." | ||
*/ | ||
export default _default; |
@@ -12,10 +12,11 @@ "use strict"; | ||
*/ | ||
const data = {}; | ||
exports.default = { | ||
data: new Map(), | ||
get(key, defaultValue) { | ||
var _a; | ||
try { | ||
return (_a = data[key]) !== null && _a !== void 0 ? _a : parseJSON(localStorage.getItem(key)); | ||
return this.data.has(key) | ||
? this.data.get(key) | ||
: parseJSON(localStorage.getItem(key)); | ||
} | ||
catch (_b) { | ||
catch (_a) { | ||
return defaultValue; | ||
@@ -27,12 +28,10 @@ } | ||
localStorage.setItem(key, JSON.stringify(value)); | ||
data[key] = undefined; | ||
return true; | ||
this.data.delete(key); | ||
} | ||
catch (_a) { | ||
data[key] = value; | ||
return false; | ||
this.data.set(key, value); | ||
} | ||
}, | ||
remove(key) { | ||
data[key] = undefined; | ||
this.data.delete(key); | ||
localStorage.removeItem(key); | ||
@@ -39,0 +38,0 @@ }, |
@@ -1,4 +0,2 @@ | ||
import { LocalStorageProperties, UpdateState } from './useLocalStorageStateBase'; | ||
export default function useLocalStorageState(key: string): [unknown, UpdateState<unknown>, LocalStorageProperties]; | ||
export default function useLocalStorageState<T = undefined>(key: string): [T | undefined, UpdateState<T | undefined>, LocalStorageProperties]; | ||
export default function useLocalStorageState<T>(key: string, defaultValue: T | (() => T)): [T, UpdateState<T>, LocalStorageProperties]; | ||
import { LocalStorageProperties, UpdateState } from './createLocalStorageHook'; | ||
export default function useLocalStorageState<T = undefined>(key: string, defaultValue: T): [T | undefined, UpdateState<T | undefined>, LocalStorageProperties]; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const storage_1 = __importDefault(require("./storage")); | ||
const react_dom_1 = require("react-dom"); | ||
const react_1 = require("react"); | ||
const useLocalStorageStateBase_1 = require("./useLocalStorageStateBase"); | ||
/** | ||
* Used to track usages of `useLocalStorageState()` with identical `key` values. If we encounter | ||
* duplicates we throw an error to the user telling them to use `createLocalStorageStateHook` | ||
* instead. | ||
*/ | ||
const initializedStorageKeys = new Set(); | ||
// `activeHooks` holds all active hooks. we use the array to update all hooks with the same key — | ||
// calling `setValue` of one hook triggers an update for all other hooks with the same key | ||
const activeHooks = []; | ||
function useLocalStorageState(key, defaultValue) { | ||
const value = (0, useLocalStorageStateBase_1.default)(key, defaultValue); | ||
/** | ||
* Detects incorrect usage of the library and throws an error with a suggestion how to fix it. | ||
*/ | ||
// `id` changes every time a change in the `localStorage` occurs | ||
const [id, forceUpdate] = (0, react_1.useReducer)((number) => number + 1, 0); | ||
// initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26 | ||
// issues that were caused by incorrect initial and secondary implementations: | ||
// - https://github.com/astoilkov/use-local-storage-state/issues/30 | ||
// - https://github.com/astoilkov/use-local-storage-state/issues/33 | ||
if (defaultValue !== undefined && | ||
!storage_1.default.data.has(key) && | ||
localStorage.getItem(key) === null) { | ||
storage_1.default.set(key, defaultValue); | ||
} | ||
// - syncs change across tabs, windows, iframe's | ||
// - the `storage` event is called only in all tabs, windows, iframe's except the one that | ||
// triggered the change | ||
(0, react_1.useEffect)(() => { | ||
if (initializedStorageKeys.has(key)) { | ||
throw new Error(`When using the same key in multiple places use createLocalStorageStateHook('${key}'): ` + | ||
`https://github.com/astoilkov/use-local-storage-state#create-local-storage-state-hook`); | ||
} | ||
else { | ||
initializedStorageKeys.add(key); | ||
} | ||
return () => void initializedStorageKeys.delete(key); | ||
const onStorage = (e) => { | ||
if (e.storageArea === localStorage && e.key === key) { | ||
forceUpdate(); | ||
} | ||
}; | ||
window.addEventListener('storage', onStorage); | ||
return () => window.removeEventListener('storage', onStorage); | ||
// `key` never changes | ||
}, [key]); | ||
return value; | ||
// add this hook to the `activeHooks` array. see the `activeHooks` declaration above for a | ||
// more detailed explanation | ||
(0, react_1.useEffect)(() => { | ||
const entry = { key, forceUpdate }; | ||
activeHooks.push(entry); | ||
return () => { | ||
activeHooks.splice(activeHooks.indexOf(entry), 1); | ||
}; | ||
// both `key` and `forceUpdate` never change | ||
}, [key, forceUpdate]); | ||
return (0, react_1.useMemo)(() => [ | ||
storage_1.default.get(key, defaultValue), | ||
(newValue) => { | ||
const isCallable = (value) => typeof value === 'function'; | ||
const newUnwrappedValue = isCallable(newValue) | ||
? newValue(storage_1.default.get(key, defaultValue)) | ||
: newValue; | ||
storage_1.default.set(key, newUnwrappedValue); | ||
(0, react_dom_1.unstable_batchedUpdates)(() => { | ||
for (const update of activeHooks) { | ||
if (update.key === key) { | ||
update.forceUpdate(); | ||
} | ||
} | ||
}); | ||
}, | ||
{ | ||
isPersistent: !storage_1.default.data.has(key), | ||
removeItem() { | ||
storage_1.default.remove(key); | ||
for (const update of activeHooks) { | ||
if (update.key === key) { | ||
update.forceUpdate(); | ||
} | ||
} | ||
}, | ||
}, | ||
], | ||
// disabling eslint warning for the following reasons: | ||
// - `id` is needed because when it changes that means the data in `localStorage` has | ||
// changed and we need to update the returned value. However, the eslint rule wants us to | ||
// remove the `id` from the dependencies array. | ||
// - `key` never changes so we can skip it and reduce package size | ||
// - `defaultValue` never changes so we can skip it and reduce package size | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[id]); | ||
} | ||
exports.default = useLocalStorageState; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
33930
23
532
189
1