├── .gitignore
├── .storybook
├── addons.js
├── config.js
├── tsconfig.json
└── webpack.config.js
├── README.md
├── package-lock.json
├── package.json
├── src
├── index.stories.tsx
└── index.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | // automatically import all files ending in *.stories.js
4 | const req = require.context('../src', true, /.stories.(js|ts|tsx)$/);
5 | function loadStories() {
6 | req.keys().forEach(filename => req(filename));
7 | }
8 |
9 | configure(loadStories, module);
10 |
--------------------------------------------------------------------------------
/.storybook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "include": [
4 | "../src"
5 | ],
6 | "exclude": [
7 | "../lib",
8 | "../node_modules"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (baseConfig, env, config) => {
2 | config.module.rules.push({
3 | test: /\.(ts|tsx)$/,
4 | use: [{
5 | loader: require.resolve('awesome-typescript-loader'),
6 | options: {
7 | configFileName: './.storybook/tsconfig.json'
8 | }
9 | }]
10 | });
11 | config.resolve.extensions.push('.ts', '.tsx');
12 | return config;
13 | };
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-use-local-storage
2 |
3 | React hook that persists state with the [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) API. It also automatically syncs state between tabs/windows.
4 |
5 | ## Getting started
6 |
7 | ```
8 | npm i @illinois/react-use-local-storage
9 | ```
10 |
11 | This hook functions similarly to `useState`, with the exception of the the caveats listed below. If your state is easily encodable in JSON and you don't use lazy initialization or functional updates, it should be easy to migrate from `useState`. You'll need to provide a key for getting/setting the item in local storage.
12 |
13 | ```jsx
14 | import useLocalStorage from '@illinois/react-use-local-storage'
15 |
16 | const MyComponent = () => {
17 | const [count, setCount] = useLocalStorage('value-key', 0)
18 | return (
19 | <>
20 |
Count: {count}
21 |
22 |
23 | >
24 | )
25 | }
26 | ```
27 |
28 | ## Trying it out
29 |
30 | ```sh
31 | git clone https://github.com/illinois/react-use-local-storage.git react-use-local-storage
32 | cd react-use-local-storage
33 | npm install
34 | npm run storybook
35 | ```
36 |
37 | This will launch a simple demo with a counter that can be incremented and reset. Try opening the demo in two tabs at once and watch how changes are automatically synced between them!
38 |
39 | ## Caveats/Warnings
40 |
41 | * State is serialized with `JSON.stringify` and deserialized with `JSON.parse`. This is done because the `localStorage` API doesn't support storing anything but strings at present. As such, you should pay special attention to objects that might not handle being round-tripped through JSON, e.g. a `Date` object.
42 |
43 | * Unlike `useState`, [lazy initialization](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state) is not currently supported. A PR adding support would be welcome!
44 |
45 | * Unlike `useState`, [functional updates](https://reactjs.org/docs/hooks-reference.html#functional-updates) are not currently supported. A PR adding support would be welcome!
46 |
47 | ## Prior art
48 |
49 | * https://github.com/streamich/react-use/blob/master/docs/useLocalStorage.md
50 | * https://github.com/rehooks/local-storage/blob/master/index.js
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illinois/react-use-local-storage",
3 | "version": "1.1.0",
4 | "description": "React hook that persists and syncs state with local storage",
5 | "main": "lib/index.js",
6 | "files": [
7 | "lib/"
8 | ],
9 | "scripts": {
10 | "build": "tsc",
11 | "storybook": "start-storybook -p 6006",
12 | "build-storybook": "build-storybook"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/illinois/react-use-local-storage.git"
17 | },
18 | "keywords": [
19 | "react",
20 | "hooks",
21 | "react-hooks"
22 | ],
23 | "author": "Nathan Walters (@nwalters512)",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/illinois/react-use-local-storage/issues"
27 | },
28 | "homepage": "https://github.com/illinois/react-use-local-storage#readme",
29 | "devDependencies": {
30 | "@babel/core": "^7.2.2",
31 | "@storybook/addon-actions": "^4.1.11",
32 | "@storybook/addon-links": "^4.1.11",
33 | "@storybook/addons": "^4.1.11",
34 | "@storybook/react": "^4.1.11",
35 | "@types/react": "^16.8.3",
36 | "@types/storybook__react": "^4.0.1",
37 | "awesome-typescript-loader": "^5.2.1",
38 | "babel-loader": "^8.0.5",
39 | "react": "^16.8.2",
40 | "typescript": "^3.3.3"
41 | },
42 | "peerDependencies": {
43 | "react": "^16.8.0",
44 | "react-dom": "^16.8.0"
45 | },
46 | "dependencies": {}
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 |
5 | import useLocalStorage from '../src/index'
6 |
7 | const UseLocalStorageDemo = () => {
8 | const [count, setCount] = useLocalStorage('demo-count', 0)
9 | return (
10 | <>
11 | Count: {count}
12 |
13 |
14 | >
15 | )
16 | }
17 |
18 | storiesOf('useLocalStorage', module)
19 | .add('demo', () => )
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from 'react'
2 |
3 | const isClient = typeof window !== 'undefined'
4 |
5 | const useLocalStorage = (key: string, initialValue: T): [T, (value: T) => void] => {
6 | if (!isClient) {
7 | // We're SSRing; can't use local storage here!
8 | return [initialValue, () => {}]
9 | }
10 | const [state, updateState] = useState((): T => {
11 | try {
12 | const localStorageValue = window.localStorage.getItem(key)
13 | if (localStorageValue === null) {
14 | // Initialize local storage with default state
15 | window.localStorage.setItem(key, JSON.stringify(initialValue))
16 | return initialValue
17 | } else {
18 | return JSON.parse(localStorageValue)
19 | }
20 | } catch {
21 | // User might be facing storage restrictions, or JSON
22 | // serialization/deserialization may have failed. We can just fall back
23 | // to using React state here.
24 | return initialValue
25 | }
26 | })
27 | const localStorageChanged = (e: StorageEvent) => {
28 | if (e.key === key) {
29 | updateState(JSON.parse(e.newValue as string))
30 | }
31 | }
32 | const setState = useCallback((value: T) => {
33 | window.localStorage.setItem(key, JSON.stringify(value))
34 | updateState(value)
35 | }, [key, updateState])
36 | useEffect(() => {
37 | window.addEventListener('storage', localStorageChanged)
38 | return () => {
39 | window.removeEventListener('storage', localStorageChanged)
40 | }
41 | })
42 | return [state, setState]
43 | }
44 |
45 | export default useLocalStorage
46 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2018",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "jsx": "react",
7 | "declaration": true,
8 | "pretty": true,
9 | "rootDir": "src",
10 | "sourceMap": true,
11 | "strict": true,
12 | "noUnusedLocals": false,
13 | "noUnusedParameters": false,
14 | "noImplicitReturns": true,
15 | "noImplicitAny": false,
16 | "noFallthroughCasesInSwitch": true,
17 | "outDir": "lib",
18 | "lib": ["es2018", "dom"]
19 | },
20 | "include": [
21 | "src"
22 | ],
23 | "exclude": [
24 | "node_modules",
25 | "lib",
26 | "**/*.stories.tsx"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------