/use-persisted-state-hook

Resilient state that persist across browser's sessions 📦

Primary LanguageJavaScript

npm version

Lightweight, resilient persisted useState.

Features:

  • 📦 Persist state on localStorage between browser sessions.
  • ⚛️ Automatically handle state's shape updates.
  • 🔄 Handle stale states when initial state changes.
  • ✅ Similar interface to React's official useState hook.
  • ✨ Server Side Render Support.

Installation

npm:

npm install use-persisted-state-hook

yarn:

yarn add use-persisted-state-hook

Detect Changes in Initial State

use-persisted-state-hook is the only library that handles changes in initial state gracefully. Leet's imagine that you have a hook called useLocalStorage like the one provided here. It has the same API that useState and looks like this:

function Greet() {
  const [visits, setVisits] = useLocalStorage('visits', 0)

  // Logic to update visits...

  return (
    <div>Visits count is {visits}</div>
  )
}

Now, imagine that you want to update the initial state to it stores more information:

function Greet() {
  const [visits, setVisits] = useLocalStorage('visits', { today: 0, total: 0 }))

  // Logic to update visits...

  return (
    <div>
      <div>Today's count is {visits.today}</div>
      <div>Total count is {visits.total}</div>
    </div>
  )
}

The code above works, however, there's a pitfall. A user that loaded your app after you released the first version, so the value it has stored for visits is 0 (or 1, or 5, or any integer). When they load your app again with the new logic, they'll see "Today's count is " and "Total count is ".

usePersistedState is the only library that handles this case gracefully by storing an identifier that identifies uniquely the initial state so, whenever it changes, the state it's going to be reset and you'd never have to think about this issue in the first place ✨

Usage

Simple Example

import React from 'react'
import usePersistedState from 'use-persisted-state-hook'

function Counter() {
  const [count, setCount] = usePersistedState('count', 0)

  return (
    <div>
      <div>Count is {count}</div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  )
}

export default Counter

Elaborated Example

Expected output

Code (styles ommited):

import React from 'react'
import usePersistedState from 'use-persisted-state-hook'

function Settings() {
  const [options, setOptions] = usePersistedState('options', [
    { title: 'Dark Mode', name: 'dark_mode', enabled: true },
    { title: 'Data Saving 2', name: 'data_saving', enabled: true },
  ])

  const onClick = (e) => {
    setOptions(
      options.map((option) =>
        option.name === e.target.name
          ? { ...option, enabled: !option.enabled }
          : option
      )
    )
  }

  return (
    <div>
      {options.map((option) => (
        <label>
          <input
            type="checkbox"
            name={option.name}
            checked={option.enabled}
            onClick={onClick}
          />{' '}
          {option.title}
        </label>
      ))}
    </div>
  )
}