New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

use-local-storage-state

Package Overview
Dependencies
Maintainers
1
Versions
83
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

use-local-storage-state - npm Package Compare versions

Comparing version 13.0.0 to 14.0.0-0

es/src/createLocalStorageHook.js

6

es/index.js

@@ -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;
"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
}
]
}
# `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;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc