Skip to content

Commit 053df4c

Browse files
committed
✨ initial implementation
1 parent 8476cbc commit 053df4c

8 files changed

+1155
-26
lines changed

package.json

+62-12
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,97 @@
11
{
2-
"name": "{{data.name}}",
3-
"version": "0.0.0",
4-
"description": "{{data.description}}",
2+
"name": "use-storage-state",
3+
"version": "1.0.0",
4+
"description": "React hook that you can wire with any Storage compatible API like `localStorage`, `sessionStorage`, or a custom one.",
55
"license": "MIT",
66
"repository": {
77
"type": "git",
8-
"url": "git+https://github.com/astoilkov/{{data.name}}"
8+
"url": "git+https://github.com/astoilkov/use-storage-state.git"
99
},
1010
"funding": "https://github.com/sponsors/astoilkov",
11+
"homepage": "https://github.com/astoilkov/use-storage-state",
1112
"author": {
1213
"name": "Antonio Stoilkov",
1314
"email": "hello@astoilkov.com",
1415
"url": "https://astoilkov.com"
1516
},
1617
"keywords": [
17-
"{{data.keywords}}"
18+
"react",
19+
"hook",
20+
"Storage",
21+
"localStorage",
22+
"sessionStorage",
23+
"persistent",
24+
"state",
25+
"useState",
26+
"hooks",
27+
"local storage",
28+
"session storage",
29+
"store"
1830
],
1931
"type": "module",
2032
"exports": {
2133
"types": "./index.d.ts",
2234
"default": "./index.js"
2335
},
2436
"sideEffects": false,
25-
"engines": {
26-
"node": ">=16"
27-
},
2837
"scripts": {
2938
"build": "tsc",
39+
"size": "yarn build && size-limit",
3040
"test": "yarn build && vitest run --coverage",
31-
"release": "yarn build && np"
41+
"release": "yarn build && np",
42+
"prettier": "prettier --write --config .prettierrc.yaml {*.ts,*.json}"
43+
},
44+
"engines": {
45+
"node": ">=14"
3246
},
3347
"files": [
3448
"index.js",
3549
"index.d.ts",
3650
"src/**/*.js",
3751
"src/**/*.d.ts"
3852
],
53+
"peerDependencies": {
54+
"react": ">=18",
55+
"react-dom": ">=18"
56+
},
3957
"devDependencies": {
58+
"@size-limit/preset-small-lib": "^11.1.1",
59+
"@testing-library/react": "^14.0.0",
60+
"@types/react": "^18.2.67",
61+
"@types/react-dom": "^18.2.22",
62+
"@typescript-eslint/eslint-plugin": "^7.2.0",
63+
"@typescript-eslint/parser": "^7.2.0",
4064
"@vitest/coverage-v8": "^1.4.0",
41-
"jsdom": "^24.0.0",
42-
"np": "^10.0.1",
65+
"confusing-browser-globals": "^1.0.11",
66+
"eslint": "^8.21.0",
67+
"eslint-config-strictest": "^0.8.1",
68+
"eslint-formatter-pretty": "^5.0.0",
69+
"eslint-plugin-promise": "^6.0.0",
70+
"eslint-plugin-react": "^7.29.4",
71+
"eslint-plugin-react-hooks": "^4.5.0",
72+
"eslint-plugin-unicorn": "^43.0.2",
73+
"jsdom": "^22.1.0",
74+
"np": "^7.6.3",
4375
"prettier": "^3.2.5",
76+
"react": "^18.2.0",
77+
"react-dom": "^18.2.0",
78+
"react-test-renderer": "^18.1.0",
79+
"size-limit": "^11.1.1",
80+
"superjson": "^2.2.1",
4481
"typescript": "^5.4.2",
4582
"vitest": "^1.4.0"
46-
}
83+
},
84+
"size-limit": [
85+
{
86+
"name": "import *",
87+
"path": "index.js",
88+
"limit": "1.75 kB",
89+
"brotli": false
90+
},
91+
{
92+
"name": "import *",
93+
"path": "index.js",
94+
"limit": "800 B"
95+
}
96+
]
4797
}

readme.md

+143-6
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,159 @@
1-
# `{{data.name}}`
1+
# `use-storage-state`
22

3-
> {{data.description}}
3+
> React hook that you can wire with any [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) compatible API like `localStorage`, `sessionStorage`, or a custom one.
44
5-
[![Gzipped Size](https://img.shields.io/bundlephobia/minzip/{{data.name}})](https://bundlephobia.com/result?p={{data.name}})
6-
[![Build Status](https://img.shields.io/github/actions/workflow/status/astoilkov/{{data.name}}/main.yml?branch=main)](https://github.com/astoilkov/{{data.name}}/actions/workflows/main.yml)
5+
[![Downloads](https://img.shields.io/npm/dm/use-storage-state)](https://www.npmjs.com/package/use-storage-state)
6+
[![Gzipped Size](https://img.shields.io/bundlephobia/minzip/use-storage-state)](https://bundlephobia.com/result?p=use-storage-state)
7+
[![Build Status](https://img.shields.io/github/actions/workflow/status/astoilkov/use-storage-state/main.yml?branch=main)](https://github.com/astoilkov/use-storage-state/actions/workflows/main.yml)
78

89
## Install
910

1011
```bash
11-
npm install {{data.name}}
12+
npm install use-storage-state
1213
```
1314

1415
## Why
1516

17+
- SSR support.
18+
- Works with React 18 concurrent rendering and React 19.
19+
- 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 `storageSync: false`.
20+
- Aiming for high-quality with [my open-source principles](https://astoilkov.com/my-open-source-principles).
21+
1622
## Usage
1723

24+
```typescript
25+
import useStorageState from 'use-storage-state'
26+
27+
export default function Todos() {
28+
const [todos, setTodos] = useStorageState('todos', {
29+
defaultValue: ['buy avocado', 'do 50 push-ups']
30+
})
31+
}
32+
```
33+
34+
<details>
35+
<summary>Todo list example + CodeSandbox link</summary>
36+
<p></p>
37+
38+
You can experiment with the example [here](https://codesandbox.io/s/todos-example-use-storage-state-tzbfhl?file=/src/App.tsx).
39+
40+
```tsx
41+
import React, { useState } from 'react'
42+
import useStorageState from 'use-storage-state'
43+
44+
export default function Todos() {
45+
const [todos, setTodos] = useStorageState('todos', {
46+
defaultValue: ['buy avocado']
47+
})
48+
const [query, setQuery] = useState('')
49+
50+
function onClick() {
51+
setQuery('')
52+
setTodos([...todos, query])
53+
}
54+
55+
return (
56+
<>
57+
<input value={query} onChange={e => setQuery(e.target.value)} />
58+
<button onClick={onClick}>Create</button>
59+
{todos.map(todo => (
60+
<div>{todo}</div>
61+
))}
62+
</>
63+
)
64+
}
65+
66+
```
67+
68+
</details>
69+
70+
<details>
71+
<summary id="remove-item">Removing the data from <code>localStorage</code> and resetting to the default</summary>
72+
<p></p>
73+
74+
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.
75+
76+
```tsx
77+
import useStorageState from 'use-storage-state'
78+
79+
export default function Todos() {
80+
const [todos, setTodos, removeItem] = useStorageState('todos', {
81+
defaultValue: ['buy avocado']
82+
})
83+
84+
function onClick() {
85+
removeItem()
86+
}
87+
}
88+
```
89+
90+
</details>
91+
92+
<details>
93+
<summary>Why my component renders twice?</summary>
94+
<p></p>
95+
96+
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. This has been discussed in the issues: https://github.com/astoilkov/use-storage-state/issues/56.
97+
98+
If you want to know if you are currently rendering the server value you can use this helper function:
99+
```ts
100+
function useIsServerRender() {
101+
return useSyncExternalStore(() => {
102+
return () => {}
103+
}, () => false, () => true)
104+
}
105+
```
106+
107+
</details>
108+
18109
## API
19110

20-
## Alternatives
111+
#### `useStorageState(key: string, options?: StorageStateOptions)`
112+
113+
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.
114+
115+
#### `key`
116+
117+
Type: `string`
118+
119+
The key used when calling `storage.setItem(key)` and `storage.getItem(key)`.
120+
121+
⚠️ 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.
122+
123+
#### `options.defaultValue`
124+
125+
Type: `any`
126+
127+
Default: `undefined`
128+
129+
The default value. You can think of it as the same as `useState(defaultValue)`.
130+
131+
### `options.storage`
132+
133+
Type: [`Storage`](https://developer.mozilla.org/en-US/docs/Web/API/Storage)
134+
135+
Default: `localStorage` (if `localStorage` is disabled in Safari it fallbacks to `sessionStorage`).
136+
137+
You can set `localStorage`, `sessionStorage`, or other any [`Storage`](https://developer.mozilla.org/en-US/docs/Web/API/Storage) compatible class like [`memorystorage`](https://github.com/download/memorystorage).
138+
139+
#### `options.storageSync`
140+
141+
Type: `boolean`
142+
143+
Default: `true`
144+
145+
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.
146+
147+
#### `options.serializer`
148+
149+
Type: `{ stringify, parse }`
150+
151+
Default: `JSON`
152+
153+
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.
21154

22155
## Related
156+
157+
- [`use-local-storage-state`](https://github.com/astoilkov/use-local-storage-state) — Similar to this hook but for `localStorage` only.
158+
- [`use-session-storage-state`](https://github.com/astoilkov/use-session-storage-state) — Similar to this hook but for `sessionStorage` only.
159+
- [`local-db-storage`](https://github.com/astoilkov/local-db-storage) — Tiny wrapper around `IndexedDB` that mimics `localStorage` API.

0 commit comments

Comments
 (0)