GithubHelp home page GithubHelp logo

soyguijarro / react-storage-hooks Goto Github PK

View Code? Open in Web Editor NEW
167.0 3.0 18.0 3.4 MB

React hooks for persistent state

Home Page: https://npm.im/react-storage-hooks

License: MIT License

TypeScript 100.00%
react hooks react-hooks persistent storage localstorage sessionstorage

react-storage-hooks's Introduction

react-storage-hooks

Version Dependencies Dev dependencies Build status Test coverage Bundle size MIT licensed

Custom React hooks for keeping application state in sync with localStorage or sessionStorage.

πŸ“– Familiar API. You already know how to use this library! Replace useState and useReducer hooks with the ones in this library and get persistent state for free.

✨ Fully featured. Automatically stringifies and parses values coming and going to storage, keeps state in sync between tabs by listening to storage events and handles non-straightforward use cases correctly.

⚑ Tiny and fast. Less than 700 bytes gzipped, enforced with size-limit. No external dependencies. Only reads from storage when necessary and writes to storage after rendering.

πŸ”  Completely typed. Written in TypeScript. Type definitions included and verified with tsd.

πŸ’ͺ Backed by tests. Full coverage of the API.

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 or sessionStorage) 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 with Error objects 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.

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.

react-storage-hooks's People

Contributors

jgornick avatar soyguijarro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

react-storage-hooks's Issues

Can't install with React 18

% npm install --save react-storage-hooks
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR!   react@"^18.2.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0" from [email protected]
npm ERR! node_modules/react-storage-hooks
npm ERR!   react-storage-hooks@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

How to invalidate storage after some time (expiration feature)?

Hi, first thanks for this great library!

I'm using this library to store a cart-like state inside my CartComponent:

const [cart, dispatch] = useStorageReducer(localStorage, 'cart', cartReducer, {
  createdAt: Math.floor(Date.now() / 1000),
  items: []
});

I'm wondering, how to invalidate the storage after 1 hour, for example? In other words, useStorageReducer should return { items: [] } after 1 hour, re-setting the state eventually stored in the localStorage object.

Is this possible? Thanks!

useLocalStorageReducer state by default is undefined

   const MyComponent = () => {
     const [state, dispatch, writeError] = useLocalStorageReducer(
        key,
        reducer,
       {
            count: 1
       }
     );

     console.log(state)
     return null
  }

This initially logs state as undefined a few times until dispatch is called, which is causing unexpected behaviour with my code.

Is this intentional?

infinite loop / browser hanging

Thanks for this library!

We use the useStorageState hook to persist some state to local storage while the user is typing, so we can restore it on refresh.

Recently, we received a report that when they typed, the browser locked up and had to be restarted. They're not able to reproduce it reliably, and we haven't been able to reproduce it easier.

I was able to reproduce it synthetically by adding

window.localStorage.setItem = () => {
  throw new Error('my error ' + Date.now())
}

where this library goes into an infinite loop trying to handle the error (even if you're not writing to local storage). This might be a red herring, or might be the root cause.

Thoughts?

Downstream issue: tilt-dev/tilt#4618

Infinite loop on any dispatch

First of all, i would like to thank that you have created this library.

We tried to use it with React ^16.9.0-alpha.0
we created a simple test component and with a state and a counter. When the button is pushed we dispatch an update to the counter. the screen keeps updating the counter. jumping between counter and counter+1. The session storage contains the right value.

code to reproduce:

import 'react-app-polyfill/ie11';
import 'ts-polyfill';

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {useSessionStorageReducer} from 'react-storage-hooks';

function reducer(state, action) {
  return action;
}

function TestComponent() {
  const [state, dispatch] = useSessionStorageReducer('key', reducer, {some: 'value', counter: 0});
  return <button onClick={() => dispatch({...state, counter: state.counter + 1})}>{JSON.stringify(state)}</button>
}

ReactDOM.render(<TestComponent/>, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Thanks for your time and effort

[BUG] State not syncing with React 17

What's the problem

When having multiple instance with same localStorage entry, only one of them will sync with localStorage.

Expected behavior

All instances will sync with localStorage as it is said in the documentation.

Automatically stringifies and parses values coming and going to storage, keeps state in sync between tabs by listening to storage events and handles non-straightforward use cases correctly.

How to reproduce

Repeat the actions in the GIF with this sand box
https://codesandbox.io/s/react-storage-hooks-syincing-problem-7dgz5

react-storage-hooks-syincing-problem

ENV:

windows 1902
chrome 91
react 17

SSR hydration warning

Hey, this module is great and we use it over at https://github.com/dayhaysoos/use-shopping-cart :) ❀️

We've had some folks run into this issue (dayhaysoos/use-shopping-cart#122) which seems to be a hydration warning because the server-side rendered value differs from the value on the client.

Have you seen that before by any chance? I did some digging, and the general recommendation is to use the same initial state as on the server, and then update it in a useEffect hook, but I'm not sure if that's possible within a custom hook πŸ€”

Reproduction steps

I've set up an environment to reproduce: https://codesandbox.io/s/autumn-grass-vhu4e?expanddevtools=1&fontsize=14&hidenavigation=1&theme=dark

  1. Navigate to CodeSandbox
  2. Switch to the Console tab (Second tab next to Terminal in the bottom right dev tools)
  3. Click the + button a couple times so counter is !== 0
  4. Click preview reload button
  5. See warning in console

image

Option to omit specific keys from storage object

We have a list of items for which we store the list filters using useReducer. We want to store certain filters using useStorageReducer but not all of them. For example the filter state looks like this:

{
	perPage: 6,
	pageNumber: 1,
	searchText: '',
	sortBy: 'id',
	view: 'card', // or 'table'
}

We want to store only view and perPage in local storage not the other keys.

Related #6

Could you provide a "sharedSessionStorage" storage object?

Could this library provide a "sharedSessionStorage" that would work just like "sessionStorage", but with synchronization between different tabs?

It could use BroadcastChannel to keep them in sync.

I think it should look like this:

const bc = new BroadcastChannel('sharedSessionStorage');
bc.onmessage = (evt) => {
  const { action, key, value } = JSON.parse(evt.data);
  switch (action) {
    case 'setItem':
      sessionStorage.setItem(key, value);
      break;
    case 'removeItem':
      sessionStorage.removeItem(key);
      break;
    default:
  }
};
export const sharedSessionStorage = {
  getItem: (key) => sessionStorage.getItem(key),
  setItem: (key, value) => {
    sessionStorage.setItem(key, value);
    bc.postMessage(JSON.stringify({ action: 'setItem', key, value }));
  },
  removeItem: (key) => {
    sessionStorage.removeItem(key);
  }
};

But it doesn't seem to notify the useStorageState hooks when a new value is set from a BroadcastChannel message.

Clearing of keys in storages is absent

Hello,

thanks for providing this hook, works good when I tested it for my use-case.

Although while working with it, there is one operation that is missing for me - clearing of the keys in various storages. I am aware, that I could just set the value of the key to an empty one (e.g. setting it to an empty string), but when I'm done with the key, I would expect to remove it altogether from these storages, as to not clutter the storage with empty keys.

Thanks in advance!

Using session reducer together with local reducer causes ie11 to freeze up

Below are the minimal code setup to reproduce the issue:

import React from 'react'
import ReactDOM from 'react-dom'
import { useSessionStorageReducer, useLocalStorageReducer } from 'react-storage-hooks'

function Root() {
  const [state1, dispatch1] = useSessionStorageReducer('session', () => {}, {})
  const [state2, dispatch2] = useLocalStorageReducer('local', () => {}, {})
  return (
    <div>hi</div>
  )
}

ReactDOM.render(<Root />, document.getElementById('root'))

Edit:
It seems that multiple useLocalStorageReducer would also crash ie11

SSR (no window or storage)

This currently can't be used in a server-side rendered app because localStorage, sessionStorage, and window don't exist.

ReferenceError: localStorage is not defined

Can we add either some mock objects or noops if these are missing?

Thanks!

Infinite Loop in ie11

Related issue: #7

reproducible case:

const Potato = () => {
  console.log("hello");
  const [state, dispatch] = useStorageReducer(
    window.sessionStorage,
    "potato",
    () => {},
    {},
  );
  return <h1>Potato</h1>;
};

ReactDOM.render(
  <StrictMode>
    <Potato />
    <Potato />
  </StrictMode>,
  document.getElementById('root'),
);

you will get an infinite amount of "hello"s in the console. Only reproducible on ie11.

Cannot import in non-TS projects

node_modules/react-storage-hooks/dist/index.js:1
(function (exports, require, module, __filename, __dirname) { import { useState, useReducer } from 'react';
                                                              ^^^^^^

SyntaxError: Unexpected token import```

Looks like the code is compiled for es6+, Could you recompile it using es5 as a target.

Warning about props not matching after page reload with SSR

I'm using Next.js with SSR.

After saving my form data in local storage and refreshing the page, my form renders with the correct values but I get the following warning, although I'm using a dummy storage object like suggested in the readme: Warning: Prop 'aria-valuenow' did not match. Server: "10000" Client: "11000".

Here is the relevant part of my code:

const dummyStorage = {
  getItem: () => null,
  setItem: () => {},
  removeItem: () => {},
};

export default function createClient() {
  const [formInitialValues, setFormInitialValues, writeErrors] =
    useStorageState(
      typeof window !== "undefined" ? localStorage : dummyStorage,
      "awaji",
      {
        lastName: "",
        firstName: "",
        gender: "male",
        dateOfBirth: "",
        initialCredit: 10000,
      }
    );

  const onSubmit = useCallback((values, actions) => {
    console.log(values);
    setFormInitialValues(values);
  }, []);

// rest of the code...

Any way to properly fix this ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.