[
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n"
  },
  {
    "path": ".npmrc",
    "content": "message=\"Version %s\""
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js: lts/*\ncache: npm\nbefore_script: npm install codecov -g\nscript: npm run check\nafter_success: codecov\ndeploy:\n  provider: npm\n  email: $NPM_EMAIL\n  api_key: $NPM_TOKEN\n  skip_cleanup: true\n  on:\n    tags: true\n    branch: master\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) Ramón Guijarro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# react-storage-hooks\n\n[![Version](https://img.shields.io/npm/v/react-storage-hooks.svg)](https://www.npmjs.com/package/react-storage-hooks)\n![Dependencies](https://img.shields.io/david/soyguijarro/react-storage-hooks.svg)\n![Dev dependencies](https://img.shields.io/david/dev/soyguijarro/react-storage-hooks.svg)\n[![Build status](https://travis-ci.com/soyguijarro/react-storage-hooks.svg?branch=master)](https://travis-ci.com/soyguijarro/react-storage-hooks)\n[![Test coverage](https://codecov.io/gh/soyguijarro/react-storage-hooks/branch/master/graph/badge.svg)](https://codecov.io/gh/soyguijarro/react-storage-hooks)\n![Bundle size](https://img.shields.io/bundlephobia/minzip/react-storage-hooks.svg)\n[![MIT licensed](https://img.shields.io/github/license/soyguijarro/react-storage-hooks.svg)](https://github.com/soyguijarro/react-storage-hooks/blob/master/LICENSE)\n\nCustom [React hooks](https://reactjs.org/docs/hooks-intro) for keeping application state in sync with `localStorage` or `sessionStorage`.\n\n:book: **Familiar API**. You already know how to use this library! Replace [`useState`](https://reactjs.org/docs/hooks-reference.html#usestate) and [`useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer) hooks with the ones in this library and get persistent state for free.\n\n:sparkles: **Fully featured**. Automatically stringifies and parses values coming and going to storage, keeps state in sync between tabs by listening to [storage events](https://developer.mozilla.org/docs/Web/API/StorageEvent) and handles non-straightforward use cases correctly.\n\n:zap: **Tiny and fast**. Less than 700 bytes gzipped, enforced with [`size-limit`](https://github.com/ai/size-limit). No external dependencies. Only reads from storage when necessary and writes to storage after rendering.\n\n:capital_abcd: **Completely typed**. Written in TypeScript. Type definitions included and verified with [`tsd`](https://github.com/SamVerschueren/tsd).\n\n:muscle: **Backed by tests**. Full coverage of the API.\n\n## Requirements\n\nYou need to use [version 16.8.0](https://github.com/facebook/react/blob/master/CHANGELOG.md#1680-february-6-2019) or greater of React, since that's the first one to include hooks. If you still need to create your application, [Create React App](https://create-react-app.dev/) is the officially supported way.\n\n## Installation\n\nAdd the package to your React project:\n\n    npm install --save react-storage-hooks\n\nOr with yarn:\n\n    yarn add react-storage-hooks\n\n## Usage\n\nThe `useStorageState` and `useStorageReducer` hooks included in this library work like [`useState`](https://reactjs.org/docs/hooks-reference.html#usestate) and [`useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer). The only but important differences are:\n\n- Two additional mandatory parameters: [**`Storage` object**](https://developer.mozilla.org/en-US/docs/Web/API/Storage) (`localStorage` or `sessionStorage`) and **storage key**.\n- Initial state parameters only apply if there's no data in storage for the provided key. Otherwise data from storage will be used as initial state. Think about it as **default** or **fallback state**.\n- The array returned by hooks has an extra last item for **write errors**. It is initially `undefined`, and will be updated with [`Error` objects](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) thrown by `Storage.setItem`. However the hook will keep updating state even if new values fail to be written to storage, to ensure that your application doesn't break.\n\n### `useStorageState`\n\n#### Example\n\n```javascript\nimport React from 'react';\nimport { useStorageState } from 'react-storage-hooks';\n\nfunction StateCounter() {\n  const [count, setCount, writeError] = useStorageState(\n    localStorage,\n    'state-counter',\n    0\n  );\n\n  return (\n    <>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>+</button>\n      <button onClick={() => setCount(count - 1)}>-</button>\n      {writeError && (\n        <pre>Cannot write to localStorage: {writeError.message}</pre>\n      )}\n    </>\n  );\n}\n```\n\n#### Signature\n\n```typescript\nfunction useStorageState<S>(\n  storage: Storage,\n  key: string,\n  defaultState?: S | (() => S)\n): [S, React.Dispatch<React.SetStateAction<S>>, Error | undefined];\n```\n\n### `useStorageReducer`\n\n#### Example\n\n```javascript\nimport React from 'react';\nimport { useStorageReducer } from 'react-storage-hooks';\n\nfunction reducer(state, action) {\n  switch (action.type) {\n    case 'inc':\n      return { count: state.count + 1 };\n    case 'dec':\n      return { count: state.count - 1 };\n    default:\n      return state;\n  }\n}\n\nfunction ReducerCounter() {\n  const [state, dispatch, writeError] = useStorageReducer(\n    localStorage,\n    'reducer-counter',\n    reducer,\n    { count: 0 }\n  );\n\n  return (\n    <>\n      <p>You clicked {state.count} times</p>\n      <button onClick={() => dispatch({ type: 'inc' })}>+</button>\n      <button onClick={() => dispatch({ type: 'dec' })}>-</button>\n      {writeError && (\n        <pre>Cannot write to localStorage: {writeError.message}</pre>\n      )}\n    </>\n  );\n}\n```\n\n#### Signature\n\n```typescript\nfunction useStorageReducer<S, A>(\n  storage: Storage,\n  key: string,\n  reducer: React.Reducer<S, A>,\n  defaultState: S\n): [S, React.Dispatch<A>, Error | undefined];\n\nfunction useStorageReducer<S, A, I>(\n  storage: Storage,\n  key: string,\n  reducer: React.Reducer<S, A>,\n  defaultInitialArg: I,\n  defaultInit: (defaultInitialArg: I) => S\n): [S, React.Dispatch<A>, Error | undefined];\n```\n\n## Advanced usage\n\n### Alternative storage objects\n\nThe `storage` parameter of the hooks can be any object that implements the `getItem`, `setItem` and `removeItem` methods of the [`Storage` interface](https://developer.mozilla.org/en-US/docs/Web/API/Storage). Keep in mind that storage values will be automatically [serialized](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) and [parsed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) before and after calling these methods.\n\n```typescript\ninterface Storage {\n  getItem(key: string): string | null;\n  setItem(key: string, value: string): void;\n  removeItem(key: string): void;\n}\n```\n\n### Server-side rendering (SSR)\n\nThis library checks for the existence of the `window` object and even has some [tests in a node-like environment](https://jestjs.io/docs/en/configuration#testenvironment-string). However in your server code you will need to provide a storage object to the hooks that works server-side. A simple solution is to use a dummy object like this:\n\n```javascript\nconst dummyStorage = {\n  getItem: () => null,\n  setItem: () => {},\n  removeItem: () => {},\n};\n```\n\nThe important bit here is to have the `getItem` method return `null`, so that the default state parameters of the hooks get applied as initial state.\n\n### Convenience custom hook\n\nIf you're using a few hooks in your application with the same type of storage, it might bother you to have to specify the storage object all the time. To alleviate this, you can write a custom hook like this:\n\n```javascript\nimport { useStorageState } from 'react-storage-hooks';\n\nexport function useLocalStorageState(...args) {\n  return useStorageState(localStorage, ...args);\n}\n```\n\nAnd then use it in your components:\n\n```javascript\nimport { useLocalStorageState } from './my-hooks';\n\nfunction Counter() {\n  const [count, setCount] = useLocalStorageState('counter', 0);\n\n  // Rest of the component\n}\n```\n\n## Development\n\nInstall development dependencies:\n\n    npm install\n\nTo set up the examples:\n\n    npm run examples:setup\n\nTo start a server with the examples in watch mode (reloads whenever examples or library code change):\n\n    npm run examples:watch\n\n### Tests\n\nRun tests:\n\n    npm test\n\nRun tests in watch mode:\n\n    npm run test:watch\n\nSee code coverage information:\n\n    npm run test:coverage\n\n### Publish\n\nGo to the `master` branch:\n\n    git checkout master\n\nBump the version number:\n\n    npm version [major | minor | patch]\n\nRun the release script:\n\n    npm run release\n\nAll code quality checks will run, the tagged commit generated by `npm version` will be pushed and [Travis CI](https://travis-ci.com/github/soyguijarro/react-storage-hooks) will publish the new package version to the npm registry.\n\n## License\n\nThis library is [MIT licensed](LICENSE).\n"
  },
  {
    "path": "examples/.storybook/main.js",
    "content": "module.exports = {\n  stories: ['../src/**/*.stories.tsx'],\n  addons: ['@storybook/preset-create-react-app'],\n};\n"
  },
  {
    "path": "examples/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"start-storybook -p 9009 -s public --ci\",\n    \"build\": \"build-storybook -s public\"\n  },\n  \"eslintConfig\": {\n    \"extends\": \"react-app\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@storybook/preset-create-react-app\": \"^2.1.1\",\n    \"@storybook/react\": \"^5.3.18\",\n    \"@types/react\": \"^16.9.34\",\n    \"@types/react-dom\": \"^16.9.6\",\n    \"react\": \"^16.13.1\",\n    \"react-dom\": \"^16.13.1\",\n    \"react-scripts\": \"^3.4.1\",\n    \"typescript\": \"^3.8.3\"\n  }\n}\n"
  },
  {
    "path": "examples/src/Examples.stories.tsx",
    "content": "import React from 'react';\n\nimport { useStorageState, useStorageReducer } from 'react-storage-hooks';\n\nexport function StateCounter() {\n  const [count, setCount, writeError] = useStorageState(\n    localStorage,\n    'state-counter',\n    0\n  );\n\n  return (\n    <>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>+</button>\n      <button onClick={() => setCount(count - 1)}>-</button>\n      {writeError && (\n        <pre>Cannot write to localStorage: {writeError.message}</pre>\n      )}\n    </>\n  );\n}\n\nfunction reducer(state: { count: number }, action: { type: 'inc' | 'dec' }) {\n  switch (action.type) {\n    case 'inc':\n      return { count: state.count + 1 };\n    case 'dec':\n      return { count: state.count - 1 };\n    default:\n      return state;\n  }\n}\n\nexport function ReducerCounter() {\n  const [state, dispatch, writeError] = useStorageReducer(\n    localStorage,\n    'reducer-counter',\n    reducer,\n    { count: 0 }\n  );\n\n  return (\n    <>\n      <p>You clicked {state.count} times</p>\n      <button onClick={() => dispatch({ type: 'inc' })}>+</button>\n      <button onClick={() => dispatch({ type: 'dec' })}>-</button>\n      {writeError && (\n        <pre>Cannot write to localStorage: {writeError.message}</pre>\n      )}\n    </>\n  );\n}\n\nexport default { title: 'Examples' };\n"
  },
  {
    "path": "examples/src/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "examples/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react\"\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-storage-hooks\",\n  \"version\": \"4.0.1\",\n  \"description\": \"React hooks for persistent state\",\n  \"keywords\": [\n    \"react\",\n    \"react-hooks\",\n    \"persistent\",\n    \"useState\",\n    \"useReducer\",\n    \"storage\",\n    \"localstorage\",\n    \"sessionstorage\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"lint\": \"eslint src/*.ts\",\n    \"fmt\": \"prettier --check *.md *.json src/*.ts\",\n    \"types\": \"tsd\",\n    \"test\": \"jest\",\n    \"test:watch\": \"npm test -- --watch\",\n    \"test:coverage\": \"npm test -- --coverage\",\n    \"test:staged\": \"npm test -- --findRelatedTests --bail\",\n    \"prebuild\": \"del dist\",\n    \"build\": \"tsc\",\n    \"build:watch\": \"npm run build -- --watch\",\n    \"size\": \"size-limit\",\n    \"precheck\": \"npm run build\",\n    \"check\": \"run-s lint fmt types test:coverage size\",\n    \"examples:setup\": \"cd examples && npm install && npm link ../.\",\n    \"examples:start\": \"cd examples && npm start\",\n    \"examples:watch\": \"run-p build:watch examples:start\",\n    \"prerelease\": \"npm run check\",\n    \"release\": \"git push --follow-tags origin master\"\n  },\n  \"files\": [\n    \"dist\",\n    \"dist/index.d.ts\"\n  ],\n  \"author\": \"Ramón Guijarro <hola@soyguijarro.com>\",\n  \"license\": \"MIT\",\n  \"repository\": \"https://github.com/soyguijarro/react-storage-hooks\",\n  \"peerDependencies\": {\n    \"react\": \"^16.8.0\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\"\n    }\n  },\n  \"prettier\": {\n    \"singleQuote\": true\n  },\n  \"eslintConfig\": {\n    \"parser\": \"@typescript-eslint/parser\",\n    \"plugins\": [\n      \"@typescript-eslint\",\n      \"react-hooks\"\n    ],\n    \"extends\": [\n      \"plugin:@typescript-eslint/recommended\",\n      \"plugin:react-app/recommended\",\n      \"prettier\",\n      \"prettier/@typescript-eslint\"\n    ],\n    \"rules\": {\n      \"no-console\": \"error\",\n      \"react-hooks/rules-of-hooks\": \"error\",\n      \"react-hooks/exhaustive-deps\": \"error\",\n      \"@typescript-eslint/explicit-function-return-type\": \"off\"\n    },\n    \"settings\": {\n      \"react\": {\n        \"version\": \"detect\"\n      }\n    }\n  },\n  \"lint-staged\": {\n    \"*.{md,json}\": \"prettier --write\",\n    \"*.ts\": [\n      \"prettier --write\",\n      \"eslint --fix\",\n      \"npm run test:staged\"\n    ]\n  },\n  \"jest\": {\n    \"moduleFileExtensions\": [\n      \"ts\",\n      \"js\"\n    ],\n    \"preset\": \"ts-jest\",\n    \"testMatch\": [\n      \"**/src/tests/*.test.ts\"\n    ],\n    \"coveragePathIgnorePatterns\": [\n      \"/node_modules/\",\n      \"<rootDir>/src/tests/\"\n    ],\n    \"coverageThreshold\": {\n      \"global\": {\n        \"branches\": 100,\n        \"functions\": 100,\n        \"lines\": 100,\n        \"statements\": 100\n      }\n    }\n  },\n  \"tsd\": {\n    \"directory\": \"src\"\n  },\n  \"size-limit\": [\n    {\n      \"limit\": \"700 B\",\n      \"path\": \"dist/index.js\"\n    }\n  ],\n  \"devDependencies\": {\n    \"@size-limit/preset-small-lib\": \"^4.4.5\",\n    \"@testing-library/react-hooks\": \"^3.2.1\",\n    \"@types/jest\": \"^25.2.1\",\n    \"@types/react\": \"^16.9.34\",\n    \"@typescript-eslint/eslint-plugin\": \"^2.28.0\",\n    \"@typescript-eslint/parser\": \"^2.28.0\",\n    \"del-cli\": \"^3.0.0\",\n    \"eslint\": \"^6.8.0\",\n    \"eslint-config-prettier\": \"^6.10.1\",\n    \"eslint-plugin-react-app\": \"^6.2.2\",\n    \"eslint-plugin-react-hooks\": \"^3.0.0\",\n    \"husky\": \"^4.2.5\",\n    \"jest\": \"^25.3.0\",\n    \"lint-staged\": \"^10.1.5\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^2.0.4\",\n    \"react\": \"^16.13.1\",\n    \"react-test-renderer\": \"^16.13.1\",\n    \"size-limit\": \"^4.4.5\",\n    \"ts-jest\": \"^25.4.0\",\n    \"tsd\": \"^0.11.0\",\n    \"typescript\": \"^3.8.3\"\n  }\n}\n"
  },
  {
    "path": "src/common.ts",
    "content": "import { useState, useEffect, useRef, useMemo } from 'react';\n\nexport type StorageObj = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;\n\nfunction fromStorage<T>(value: string | null) {\n  return value !== null ? (JSON.parse(value) as T) : null;\n}\n\nfunction readItem<T>(storage: StorageObj, key: string) {\n  try {\n    const storedValue = storage.getItem(key);\n    return fromStorage<T>(storedValue);\n  } catch (e) {\n    return null;\n  }\n}\n\nfunction toStorage<T>(value: T | null) {\n  return JSON.stringify(value);\n}\n\nfunction writeItem<T>(storage: StorageObj, key: string, value: T | null) {\n  try {\n    if (value !== null) {\n      storage.setItem(key, toStorage<T>(value));\n    } else {\n      storage.removeItem(key);\n    }\n    return Promise.resolve();\n  } catch (error) {\n    return Promise.reject(error);\n  }\n}\n\nexport function useInitialState<S>(\n  storage: StorageObj,\n  key: string,\n  defaultState: S\n) {\n  const defaultStateRef = useRef(defaultState);\n\n  return useMemo(() => readItem<S>(storage, key) ?? defaultStateRef.current, [\n    key,\n    storage,\n  ]);\n}\n\nexport function useStorageWriter<S>(\n  storage: StorageObj,\n  key: string,\n  state: S\n) {\n  const [writeError, setWriteError] = useState<Error | undefined>(undefined);\n\n  useEffect(() => {\n    writeItem<S>(storage, key, state).catch((error) => {\n      if (!error || !error.message || error.message !== writeError?.message) {\n        setWriteError(error);\n      }\n    });\n\n    if (writeError) {\n      return () => {\n        setWriteError(undefined);\n      };\n    }\n  }, [state, key, writeError, storage]);\n\n  return writeError;\n}\n\nexport function useStorageListener<S>(\n  storage: StorageObj,\n  key: string,\n  defaultState: S,\n  onChange: (newValue: S) => void\n) {\n  const defaultStateRef = useRef(defaultState);\n  const onChangeRef = useRef(onChange);\n\n  const firstRun = useRef(true);\n  useEffect(() => {\n    if (firstRun.current) {\n      firstRun.current = false;\n      return;\n    }\n\n    onChangeRef.current(readItem<S>(storage, key) ?? defaultStateRef.current);\n  }, [key, storage]);\n\n  useEffect(() => {\n    function onStorageChange(event: StorageEvent) {\n      if (event.key === key) {\n        onChangeRef.current(\n          fromStorage<S>(event.newValue) ?? defaultStateRef.current\n        );\n      }\n    }\n\n    if (\n      typeof window !== 'undefined' &&\n      typeof window.addEventListener !== 'undefined'\n    ) {\n      window.addEventListener('storage', onStorageChange);\n      return () => {\n        window.removeEventListener('storage', onStorageChange);\n      };\n    }\n  }, [key]);\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "export { default as useStorageState } from './state';\nexport { default as useStorageReducer } from './reducer';\n"
  },
  {
    "path": "src/reducer.ts",
    "content": "import { useReducer, Reducer, Dispatch } from 'react';\n\nimport {\n  useInitialState,\n  useStorageListener,\n  useStorageWriter,\n  StorageObj,\n} from './common';\n\nconst FORCE_STATE_ACTION = '__FORCE_STATE_INTERNAL_API__';\ntype ForceStateAction<S> = { type: typeof FORCE_STATE_ACTION; payload: S };\n\nfunction isForceStateAction<S, A>(\n  action: A | ForceStateAction<S>\n): action is ForceStateAction<S> {\n  return (\n    typeof action === 'object' &&\n    action !== null &&\n    'type' in action &&\n    action.type === FORCE_STATE_ACTION\n  );\n}\n\nfunction addForceStateActionToReducer<S, A>(reducer: Reducer<S, A>) {\n  return (state: S, action: A | ForceStateAction<S>) => {\n    if (isForceStateAction(action)) return action.payload;\n    return reducer(state, action);\n  };\n}\n\nfunction useStorageReducer<S, A>(\n  storage: StorageObj,\n  key: string,\n  reducer: Reducer<S, A>,\n  defaultState: S\n): [S, Dispatch<A>, Error | undefined];\n\nfunction useStorageReducer<S, A, I>(\n  storage: StorageObj,\n  key: string,\n  reducer: Reducer<S, A>,\n  defaultInitialArg: I,\n  defaultInit: (defaultInitialArg: I) => S\n): [S, Dispatch<A>, Error | undefined];\n\nfunction useStorageReducer<S, A, I = S>(\n  storage: StorageObj,\n  key: string,\n  reducer: Reducer<S, A>,\n  defaultInitialArg: I,\n  defaultInit: (defaultInitialArg: I | S) => S = (x) => x as S\n): [S, Dispatch<A>, Error | undefined] {\n  const defaultState = defaultInit(defaultInitialArg);\n\n  const [state, dispatch] = useReducer(\n    addForceStateActionToReducer(reducer),\n    useInitialState(storage, key, defaultState)\n  );\n\n  useStorageListener(storage, key, defaultState, (newValue: S) => {\n    dispatch({ type: FORCE_STATE_ACTION, payload: newValue });\n  });\n  const writeError = useStorageWriter(storage, key, state);\n\n  return [state, dispatch, writeError];\n}\n\nexport default useStorageReducer;\n"
  },
  {
    "path": "src/state.ts",
    "content": "import { useState, Dispatch, SetStateAction } from 'react';\n\nimport {\n  useInitialState,\n  useStorageListener,\n  useStorageWriter,\n  StorageObj,\n} from './common';\n\nfunction useStorageState<S>(\n  storage: StorageObj,\n  key: string,\n  defaultState: S | (() => S)\n): [S, Dispatch<SetStateAction<S>>, Error | undefined];\n\nfunction useStorageState<S>(\n  storage: StorageObj,\n  key: string\n): [S | null, Dispatch<SetStateAction<S | null>>, Error | undefined];\n\nfunction useStorageState<S>(\n  storage: StorageObj,\n  key: string,\n  defaultState: S | (() => S) | null = null\n) {\n  const [state, setState] = useState(\n    useInitialState(storage, key, defaultState)\n  );\n\n  useStorageListener(storage, key, defaultState, setState);\n  const writeError = useStorageWriter(storage, key, state);\n\n  return [state, setState, writeError];\n}\n\nexport default useStorageState;\n"
  },
  {
    "path": "src/tests/reducer.node.test.ts",
    "content": "/**\n * @jest-environment node\n */\n\nimport { renderHook } from '@testing-library/react-hooks';\n\nimport { useStorageReducer } from '..';\nimport { storageLikeObject } from './utils';\n\nfunction reducer(state: { value: number }) {\n  return state;\n}\n\nit('returns default state', () => {\n  const { result } = renderHook(() =>\n    useStorageReducer(storageLikeObject, 'key', reducer, {\n      value: 0,\n    })\n  );\n\n  const [state] = result.current;\n  expect(state).toStrictEqual({ value: 0 });\n});\n\nit('returns default state (lazy initialization)', () => {\n  const { result } = renderHook(() =>\n    useStorageReducer(storageLikeObject, 'key', reducer, 0, value => ({\n      value,\n    }))\n  );\n\n  const [state] = result.current;\n  expect(state).toStrictEqual({ value: 0 });\n});\n"
  },
  {
    "path": "src/tests/reducer.test-d.ts",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\n\nimport { Dispatch } from 'react';\nimport { expectType, expectError } from 'tsd';\n\nimport { useStorageReducer } from '..';\nimport { storageLikeObject } from './utils';\n\ntype State = { value: number };\ntype Action = { type: 'inc' | 'dec' };\n\nfunction reducer(state: State, action: Action) {\n  switch (action.type) {\n    case 'inc':\n      return { value: state.value + 1 };\n    case 'dec':\n      return { value: state.value - 1 };\n    default:\n      return state;\n  }\n}\n\nconst [state, dispatch, writeError] = useStorageReducer(\n  localStorage,\n  'key',\n  reducer,\n  { value: 0 }\n);\nexpectType<State>(state);\nexpectType<Dispatch<Action>>(dispatch);\nexpectType<Error | undefined>(writeError);\nexpectError(() => dispatch({ type: 'other' }));\n\nconst [otherState, otherDispatch] = useStorageReducer(\n  localStorage,\n  'key',\n  reducer,\n  0,\n  value => ({ value })\n);\nexpectType<State>(otherState);\nexpectType<Dispatch<Action>>(otherDispatch);\n\nuseStorageReducer(storageLikeObject, 'key', reducer, { value: 0 });\n\nexpectError(() => useStorageReducer());\nexpectError(() => useStorageReducer(localStorage));\nexpectError(() => useStorageReducer(localStorage, 'key'));\nexpectError(() => useStorageReducer(localStorage, 'key', reducer));\n\nexpectError(() => useStorageReducer({}, 'key', reducer, { value: 0 }));\nexpectError(() => useStorageReducer(localStorage, 0, reducer, { value: 0 }));\nexpectError(() =>\n  useStorageReducer(localStorage, 'key', () => 0, { value: 0 })\n);\nexpectError(() =>\n  useStorageReducer(localStorage, 'key', reducer, { value: 'value' })\n);\nexpectError(() =>\n  useStorageReducer(localStorage, 'key', reducer, 'value', value => ({ value }))\n);\n"
  },
  {
    "path": "src/tests/reducer.test.ts",
    "content": "import { renderHook, act } from '@testing-library/react-hooks';\n\nimport { useStorageReducer } from '..';\nimport {\n  mockStorageError,\n  mockStorageErrorOnce,\n  fireStorageEvent,\n} from './utils';\n\nafterEach(() => {\n  localStorage.clear();\n  jest.restoreAllMocks();\n});\n\nfunction reducer(state: { value: number }, action: { type: 'inc' | 'dec' }) {\n  switch (action.type) {\n    case 'inc':\n      return { value: state.value + 1 };\n    case 'dec':\n      return { value: state.value - 1 };\n    default:\n      return state;\n  }\n}\n\ndescribe('initialization', () => {\n  it('returns storage value when available', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, {\n        value: 0,\n      })\n    );\n\n    const [state] = result.current;\n    expect(state).toStrictEqual({ value: 1 });\n  });\n\n  it('returns default state when storage empty and writes it to storage', () => {\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n\n    const [state] = result.current;\n    expect(state).toStrictEqual({ value: 0 });\n    expect(localStorage.getItem('key')).toBe('{\"value\":0}');\n  });\n\n  it('returns default state when storage empty and writes it to storage (lazy initialization)', () => {\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, 0, value => ({ value }))\n    );\n\n    const [state] = result.current;\n    expect(state).toStrictEqual({ value: 0 });\n    expect(localStorage.getItem('key')).toBe('{\"value\":0}');\n  });\n\n  it('returns default state when storage reading fails', () => {\n    mockStorageErrorOnce(localStorage, 'getItem', 'Error message');\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n\n    const {\n      current: [state],\n    } = result;\n    expect(state).toStrictEqual({ value: 0 });\n  });\n});\n\ndescribe('updates', () => {\n  it('returns new state and writes to storage', () => {\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n    const [, dispatch] = result.current;\n    act(() => dispatch({ type: 'inc' }));\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n    expect(localStorage.getItem('key')).toBe('{\"value\":1}');\n  });\n\n  it('returns new state and write error when storage writing fails once', async () => {\n    mockStorageErrorOnce(localStorage, 'setItem', 'Error message');\n\n    const { result, waitForNextUpdate } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n    const [, dispatch] = result.current;\n    act(() => dispatch({ type: 'inc' }));\n    await waitForNextUpdate();\n\n    const [newState, , writeError] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n    expect(writeError).toEqual(Error('Error message'));\n  });\n\n  it('returns new state and previous write error when storage writing fails multiple times', async () => {\n    mockStorageError(localStorage, 'setItem', 'Error message');\n\n    const { result, waitForNextUpdate } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n    const [, dispatch] = result.current;\n    act(() => dispatch({ type: 'inc' }));\n    await waitForNextUpdate();\n\n    const [, newDispatch, writeError] = result.current;\n    expect(writeError).toEqual(Error('Error message'));\n\n    act(() => newDispatch({ type: 'inc' }));\n    await waitForNextUpdate();\n\n    const [, , newWriteError] = result.current;\n    expect(newWriteError).toEqual(Error('Error message'));\n  });\n\n  it('returns new state and no previous write error when storage writing works after failing', async () => {\n    mockStorageErrorOnce(localStorage, 'setItem', 'Error message');\n\n    const { result, waitForNextUpdate } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n    const [, dispatch] = result.current;\n    act(() => dispatch({ type: 'inc' }));\n    await waitForNextUpdate();\n\n    const [, newDispatch, writeError] = result.current;\n    expect(writeError).toEqual(Error('Error message'));\n\n    act(() => newDispatch({ type: 'inc' }));\n\n    const [, , newWriteError] = result.current;\n    expect(newWriteError).toBeUndefined();\n  });\n\n  it('returns same state when default state changes', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result, rerender } = renderHook(\n      defaultState =>\n        useStorageReducer(localStorage, 'key', reducer, defaultState),\n      { initialProps: { value: 0 } }\n    );\n    rerender({ value: 2 });\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n  });\n\n  it('returns same state when storage empty and default state changes', () => {\n    const { result, rerender } = renderHook(\n      defaultState =>\n        useStorageReducer(localStorage, 'key', reducer, defaultState),\n      { initialProps: { value: 0 } }\n    );\n    rerender({ value: 1 });\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n\n  it('returns new state when storage event fired for key', () => {\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n\n    act(() => fireStorageEvent('key', '{\"value\":1}'));\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n  });\n\n  it('returns same state when storage event fired for key and storage empty', () => {\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n\n    act(() => fireStorageEvent('key', null));\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n\n  it('returns same state when storage event fired for other key', () => {\n    const { result } = renderHook(() =>\n      useStorageReducer(localStorage, 'key', reducer, { value: 0 })\n    );\n\n    act(() => {\n      fireStorageEvent('other-key', '{\"value\":1}');\n    });\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n});\n\ndescribe('resetting', () => {\n  it('returns new storage value when key changes and storage value available', () => {\n    localStorage.setItem('new-key', '{\"value\":1}');\n\n    const { result, rerender } = renderHook(\n      key => useStorageReducer(localStorage, key, reducer, { value: 0 }),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n  });\n\n  it('returns default state when key changes and storage empty', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result, rerender } = renderHook(\n      key => useStorageReducer(localStorage, key, reducer, { value: 0 }),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n\n  it('returns no previous write error when key changes', async () => {\n    mockStorageErrorOnce(localStorage, 'setItem', 'Error message');\n\n    const { result, rerender, waitForNextUpdate } = renderHook(\n      key => useStorageReducer(localStorage, key, reducer, { value: 0 }),\n      { initialProps: 'key' }\n    );\n    const [, dispatch] = result.current;\n    act(() => dispatch({ type: 'inc' }));\n    await waitForNextUpdate();\n\n    const [, , writeError] = result.current;\n    expect(writeError).toEqual(Error('Error message'));\n\n    rerender('new-key');\n\n    const [, , newWriteError] = result.current;\n    expect(newWriteError).toBeUndefined();\n  });\n\n  it('writes to new key when key changes', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n    localStorage.setItem('new-key', '{\"value\":2}');\n\n    const { result, rerender } = renderHook(\n      key => useStorageReducer(localStorage, key, reducer, { value: 0 }),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n    const [, dispatch] = result.current;\n    act(() => dispatch({ type: 'inc' }));\n\n    expect(localStorage.getItem('key')).toStrictEqual('{\"value\":1}');\n    expect(localStorage.getItem('new-key')).toStrictEqual('{\"value\":3}');\n  });\n});\n"
  },
  {
    "path": "src/tests/state.node.test.ts",
    "content": "/**\n * @jest-environment node\n */\n\nimport { renderHook } from '@testing-library/react-hooks';\n\nimport { useStorageState } from '..';\nimport { storageLikeObject } from './utils';\n\nit('returns default state', () => {\n  const { result } = renderHook(() =>\n    useStorageState(storageLikeObject, 'key', { value: 0 })\n  );\n  const [state] = result.current;\n  expect(state).toStrictEqual({ value: 0 });\n});\n\nit('returns default state (lazy initialization)', () => {\n  const { result } = renderHook(() =>\n    useStorageState(storageLikeObject, 'key', () => ({ value: 0 }))\n  );\n\n  const [state] = result.current;\n  expect(state).toStrictEqual({ value: 0 });\n});\n"
  },
  {
    "path": "src/tests/state.test-d.ts",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\n\nimport { Dispatch, SetStateAction } from 'react';\nimport { expectType, expectError } from 'tsd';\n\nimport { useStorageState } from '..';\nimport { storageLikeObject } from './utils';\n\ntype SetState<S> = Dispatch<SetStateAction<S>>;\n\nconst [inferredString, setInferredString, writeError] = useStorageState(\n  localStorage,\n  'key',\n  'test'\n);\nexpectType<string>(inferredString);\nexpectType<SetState<string>>(setInferredString);\nexpectType<Error | undefined>(writeError);\nexpectError(() => setInferredString(0));\n\nconst [inferredNumber, setInferredNumber] = useStorageState(\n  localStorage,\n  'key',\n  0\n);\nexpectType<number>(inferredNumber);\nexpectType<SetState<number>>(setInferredNumber);\nexpectError(() => setInferredNumber('test'));\n\nconst [inferredNumberLazy, setInferredNumberLazy] = useStorageState(\n  localStorage,\n  'key',\n  () => 0\n);\nexpectType<number>(inferredNumberLazy);\nexpectType<SetState<number>>(setInferredNumberLazy);\nexpectError(() => setInferredNumberLazy('test'));\n\nconst [declaredNumber, setDeclaredNumber] = useStorageState<number>(\n  localStorage,\n  'key'\n);\nexpectType<number | null>(declaredNumber);\nexpectType<SetState<number | null>>(setDeclaredNumber);\nexpectError(() => setDeclaredNumber('test'));\n\nconst [unknown, setUnknown] = useStorageState(localStorage, 'key');\nexpectType<unknown>(unknown);\nexpectType<SetState<unknown>>(setUnknown);\n\nuseStorageState(storageLikeObject, 'key', 0);\n\nexpectError(() => useStorageState());\nexpectError(() => useStorageState(localStorage));\n\nexpectError(() => useStorageState({}, 'key'));\nexpectError(() => useStorageState(localStorage, 0));\n"
  },
  {
    "path": "src/tests/state.test.ts",
    "content": "import { renderHook, act } from '@testing-library/react-hooks';\n\nimport { useStorageState } from '..';\nimport {\n  mockStorageError,\n  mockStorageErrorOnce,\n  fireStorageEvent,\n} from './utils';\n\nafterEach(() => {\n  localStorage.clear();\n  jest.restoreAllMocks();\n});\n\ndescribe('initialization', () => {\n  it('returns storage value when available', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n\n    const [state] = result.current;\n    expect(state).toStrictEqual({ value: 1 });\n  });\n\n  it('returns default state when storage empty and writes it to storage', () => {\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n\n    const [state] = result.current;\n    expect(state).toStrictEqual({ value: 0 });\n    expect(localStorage.getItem('key')).toBe('{\"value\":0}');\n  });\n\n  it('returns default state when storage empty and writes it to storage (lazy initialization)', () => {\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', () => ({ value: 0 }))\n    );\n\n    const [state] = result.current;\n    expect(state).toStrictEqual({ value: 0 });\n    expect(localStorage.getItem('key')).toBe('{\"value\":0}');\n  });\n\n  it('returns null when storage empty and no default provided', () => {\n    const { result } = renderHook(() => useStorageState(localStorage, 'key'));\n\n    const [state] = result.current;\n    expect(state).toBeNull();\n  });\n\n  it('returns default state when storage reading fails', () => {\n    mockStorageErrorOnce(localStorage, 'getItem', 'Error message');\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n\n    const {\n      current: [state],\n    } = result;\n    expect(state).toStrictEqual({ value: 0 });\n  });\n\n  it('returns null when storage reading fails and no default provided', () => {\n    mockStorageErrorOnce(localStorage, 'getItem', 'Error message');\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result } = renderHook(() => useStorageState(localStorage, 'key'));\n\n    const {\n      current: [state],\n    } = result;\n    expect(state).toBeNull();\n  });\n});\n\ndescribe('updates', () => {\n  it('returns new state and writes to storage', () => {\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n    const [, setState] = result.current;\n    act(() => setState({ value: 1 }));\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n    expect(localStorage.getItem('key')).toBe('{\"value\":1}');\n  });\n\n  it('returns new state and write error when storage writing fails once', async () => {\n    mockStorageErrorOnce(localStorage, 'setItem', 'Error message');\n\n    const { result, waitForNextUpdate } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n    const [, setState] = result.current;\n    act(() => setState({ value: 1 }));\n    await waitForNextUpdate();\n\n    const [newState, , writeError] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n    expect(writeError).toEqual(Error('Error message'));\n  });\n\n  it('returns new state and previous write error when storage writing fails multiple times', async () => {\n    mockStorageError(localStorage, 'setItem', 'Error message');\n\n    const { result, waitForNextUpdate } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n    const [, setState] = result.current;\n    act(() => setState({ value: 1 }));\n    await waitForNextUpdate();\n\n    const [, newSetState, writeError] = result.current;\n    expect(writeError).toEqual(Error('Error message'));\n\n    act(() => newSetState({ value: 2 }));\n    await waitForNextUpdate();\n\n    const [, , newWriteError] = result.current;\n    expect(newWriteError).toEqual(Error('Error message'));\n  });\n\n  it('returns new state and no previous write error when storage writing works after failing', async () => {\n    mockStorageErrorOnce(localStorage, 'setItem', 'Error message');\n\n    const { result, waitForNextUpdate } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n    const [, setState] = result.current;\n    act(() => setState({ value: 1 }));\n    await waitForNextUpdate();\n\n    const [, newSetState, writeError] = result.current;\n    expect(writeError).toEqual(Error('Error message'));\n\n    act(() => newSetState({ value: 2 }));\n\n    const [, , newWriteError] = result.current;\n    expect(newWriteError).toBeUndefined();\n  });\n\n  it('returns same state when default state changes', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result, rerender } = renderHook(\n      defaultState => useStorageState(localStorage, 'key', defaultState),\n      { initialProps: { value: 0 } }\n    );\n    rerender({ value: 2 });\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n  });\n\n  it('returns same state when storage empty and default state changes', () => {\n    const { result, rerender } = renderHook(\n      defaultState => useStorageState(localStorage, 'key', defaultState),\n      { initialProps: { value: 0 } }\n    );\n    rerender({ value: 1 });\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n\n  it('returns null and removes key from storage when null provided', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result } = renderHook(() =>\n      useStorageState<{ value: number } | null>(localStorage, 'key', {\n        value: 0,\n      })\n    );\n    const [, setState] = result.current;\n    act(() => setState(null));\n\n    const [newState] = result.current;\n    expect(newState).toBeNull();\n    expect(localStorage.getItem('key')).toBeNull();\n  });\n\n  it('returns new state when storage event fired for key', () => {\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', 0)\n    );\n\n    act(() => fireStorageEvent('key', '{\"value\":1}'));\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n  });\n\n  it('returns same state when storage event fired for key and storage empty', () => {\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n\n    act(() => fireStorageEvent('key', null));\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n\n  it('returns same state when storage event fired for other key', () => {\n    const { result } = renderHook(() =>\n      useStorageState(localStorage, 'key', { value: 0 })\n    );\n\n    act(() => {\n      fireStorageEvent('other-key', '{\"value\":1}');\n    });\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n});\n\ndescribe('resetting', () => {\n  it('returns new storage value when key changes and storage value available', () => {\n    localStorage.setItem('new-key', '{\"value\":1}');\n\n    const { result, rerender } = renderHook(\n      key => useStorageState(localStorage, key, { value: 0 }),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 1 });\n  });\n\n  it('returns default state when key changes and storage empty', () => {\n    localStorage.setItem('key', '1');\n\n    const { result, rerender } = renderHook(\n      key => useStorageState(localStorage, key, { value: 0 }),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n\n    const [newState] = result.current;\n    expect(newState).toStrictEqual({ value: 0 });\n  });\n\n  it('returns null when key changes, storage empty and no default provided', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n\n    const { result, rerender } = renderHook(\n      key => useStorageState(localStorage, key),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n\n    const [newState] = result.current;\n    expect(newState).toBeNull();\n  });\n\n  it('returns no previous write error when key changes', async () => {\n    mockStorageErrorOnce(localStorage, 'setItem', 'Error message');\n\n    const { result, rerender, waitForNextUpdate } = renderHook(\n      key => useStorageState(localStorage, key, { value: 0 }),\n      { initialProps: 'key' }\n    );\n    const [, setState] = result.current;\n    act(() => setState({ value: 1 }));\n    await waitForNextUpdate();\n\n    const [, , writeError] = result.current;\n    expect(writeError).toEqual(Error('Error message'));\n\n    rerender('new-key');\n\n    const [, , newWriteError] = result.current;\n    expect(newWriteError).toBeUndefined();\n  });\n\n  it('writes to new key when key changes', () => {\n    localStorage.setItem('key', '{\"value\":1}');\n    localStorage.setItem('new-key', '{\"value\":2}');\n\n    const { result, rerender } = renderHook(\n      key => useStorageState(localStorage, key, { value: 0 }),\n      {\n        initialProps: 'key',\n      }\n    );\n    rerender('new-key');\n    const [, setState] = result.current;\n    act(() => setState({ value: 3 }));\n\n    expect(localStorage.getItem('key')).toBe('{\"value\":1}');\n    expect(localStorage.getItem('new-key')).toBe('{\"value\":3}');\n  });\n});\n"
  },
  {
    "path": "src/tests/utils.ts",
    "content": "function getStorageSpy(storage: Storage, method: 'getItem' | 'setItem') {\n  // Cannot mock Storage methods directly: https://github.com/facebook/jest/issues/6798\n  return jest.spyOn(Object.getPrototypeOf(storage), method);\n}\n\nexport function mockStorageError(\n  storage: Storage,\n  method: 'getItem' | 'setItem',\n  errorMessage: string\n) {\n  getStorageSpy(storage, method).mockImplementation(() => {\n    throw new Error(errorMessage);\n  });\n}\n\nexport function mockStorageErrorOnce(\n  storage: Storage,\n  method: 'getItem' | 'setItem',\n  errorMessage: string\n) {\n  getStorageSpy(storage, method).mockImplementationOnce(() => {\n    throw new Error(errorMessage);\n  });\n}\n\nexport function fireStorageEvent(key: string, value: string | null) {\n  window.dispatchEvent(new StorageEvent('storage', { key, newValue: value }));\n}\n\nexport const storageLikeObject = {\n  getItem: (key: string) => null,\n  /* eslint-disable @typescript-eslint/no-empty-function */\n  setItem: (key: string, value: string) => {},\n  removeItem: (key: string) => {},\n  /* eslint-enable @typescript-eslint/no-empty-function */\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"es2016\", \"dom\"],\n    \"jsx\": \"react\",\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./dist/\",\n    \"strict\": true,\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"src/index.ts\"]\n}\n"
  }
]