react-storage-hooks
Custom React hooks for keeping application state in sync with localStorage
or sessionStorage
.
useState
and useReducer
hooks with the ones in this library and get persistent state for free.
size-limit
. No external dependencies. Only reads from storage when necessary and writes to storage after rendering.
tsd
.
Requirements
You need to use version 16.8.0 or greater of React, since that's the first one to include hooks. If you still need to create your application, Create React App is the officially supported way.
Installation
Add the package to your React project:
npm install --save react-storage-hooks
Or with yarn:
yarn add react-storage-hooks
Usage
The useStorageState
and useStorageReducer
hooks included in this library work like useState
and useReducer
. The only but important differences are:
- Two additional mandatory parameters:
Storage
object (localStorage
orsessionStorage
) and storage key. - 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.
- The array returned by hooks has an extra last item for write errors. It is initially
undefined
, and will be updated withError
objects thrown byStorage.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.
useStorageState
Example
import React from 'react';
import { useStorageState } from 'react-storage-hooks';
function StateCounter() {
const [count, setCount, writeError] = useStorageState(
localStorage,
'state-counter',
0
);
return (
<>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
{writeError && (
<pre>Cannot write to localStorage: {writeError.message}</pre>
)}
</>
);
}
Signature
function useStorageState<S>(
storage: Storage,
key: string,
defaultState?: S | (() => S)
): [S, React.Dispatch<React.SetStateAction<S>>, Error | undefined];
useStorageReducer
Example
import React from 'react';
import { useStorageReducer } from 'react-storage-hooks';
function reducer(state, action) {
switch (action.type) {
case 'inc':
return { count: state.count + 1 };
case 'dec':
return { count: state.count - 1 };
default:
return state;
}
}
function ReducerCounter() {
const [state, dispatch, writeError] = useStorageReducer(
localStorage,
'reducer-counter',
reducer,
{ count: 0 }
);
return (
<>
<p>You clicked {state.count} times</p>
<button onClick={() => dispatch({ type: 'inc' })}>+</button>
<button onClick={() => dispatch({ type: 'dec' })}>-</button>
{writeError && (
<pre>Cannot write to localStorage: {writeError.message}</pre>
)}
</>
);
}
Signature
function useStorageReducer<S, A>(
storage: Storage,
key: string,
reducer: React.Reducer<S, A>,
defaultState: S
): [S, React.Dispatch<A>, Error | undefined];
function useStorageReducer<S, A, I>(
storage: Storage,
key: string,
reducer: React.Reducer<S, A>,
defaultInitialArg: I,
defaultInit: (defaultInitialArg: I) => S
): [S, React.Dispatch<A>, Error | undefined];
Advanced usage
Alternative storage objects
The storage
parameter of the hooks can be any object that implements the getItem
, setItem
and removeItem
methods of the Storage
interface. Keep in mind that storage values will be automatically serialized and parsed before and after calling these methods.
interface Storage {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}
Server-side rendering (SSR)
This library checks for the existence of the window
object and even has some tests in a node-like environment. 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:
const dummyStorage = {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
};
The 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.
Convenience custom hook
If 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:
import { useStorageState } from 'react-storage-hooks';
export function useLocalStorageState(...args) {
return useStorageState(localStorage, ...args);
}
And then use it in your components:
import { useLocalStorageState } from './my-hooks';
function Counter() {
const [count, setCount] = useLocalStorageState('counter', 0);
// Rest of the component
}
Development
Install development dependencies:
npm install
To set up the examples:
npm run examples:setup
To start a server with the examples in watch mode (reloads whenever examples or library code change):
npm run examples:watch
Tests
Run tests:
npm test
Run tests in watch mode:
npm run test:watch
See code coverage information:
npm run test:coverage
Publish
Go to the master
branch:
git checkout master
Bump the version number:
npm version [major | minor | patch]
Run the release script:
npm run release
All code quality checks will run, the tagged commit generated by npm version
will be pushed and Travis CI will publish the new package version to the npm registry.
License
This library is MIT licensed.