Skip to main content
react-md

useStorage

function useStorage<T>(options: StorageOptions<T>): StorageImplementation<T>;

The useStorage hook can be used to control a single value and persist it to localStorage.

Example Usage

The useStorage hook can be used to read and write from localStorage (default) or sessionStorage. The default behavior will automatically sync the value across tabs using the StorageEvent.

import { TextField } from "@react-md/core/form/TextField";
import { useStorage } from "@react-md/core/storage/useStorage";

function Example() {
  const { value, setValue } = useStorage({
    key: "savedSearch",
    defaultValue: "",
  });

  return (
    <TextField
      label="Search"
      placeholder="Search..."
      type="search"
      value={value}
      onChange={(event) => {
        setValue(event.currentTarget.value);
      }}
    />
  );
}

Type-safe Objects

The value can be type-safe by providing the value as a type parameter and a deserializer function that validates the value in local storage.

import { useStorage } from "@react-md/core/storage/useStorage";
import { type ReactElement } from "react";

interface ExpectedSchema {
  label: string;
  value: string;
  // others
}

function Example(): ReactElement {
  const { value, setValue } = useStorage<ExpectedSchema | null>({
    key: "someKey",
    defaultValue: null,

    // this is optional: you can create a custom deserializer to validate
    // the stored value to prevent people manually updating local storage in
    // the dev tools
    deserializer(item) {
      const parsed = JSON.parse(item);
      const { label, value } = parsed;
      if (typeof label !== "string" || typeof value !== "string") {
        return null;
      }

      return { label, value };
    },
  });

  // do something
  // value will be `ExpectedSchema | null`
}

Manual Persistence

The useStorage hook defaults to automatically saving the current value in local storage immediately. To allow manual persisting to local storage, enable the manual option and use the persist() or remove() functions.

import { Button } from "@react-md/core/button/Button";
import { Checkbox } from "@react-md/core/form/Checkbox";
import { Form } from "@react-md/core/form/Form";
import { useStorage } from "@react-md/core/storage/useStorage";
import { type ReactElement } from "react";

function Example(): ReactElement {
  const { value, setValue, remove, persist } = useStorage({
    key: "someKey",
    manual: true,
    defaultValue: false,
  });

  return (
    <Form
      onSubmit={() => {
        // current value will be saved into local storage
        persist();
      }}
      onReset={() => {
        // "someKey" will be removed from local storage
        remove();
      }}
    >
      <Checkbox
        label="Allow cookies"
        checked={value}
        onChange={(event) => {
          setValue(event.currentTarget.checked);
        }}
      />
      <Button type="reset">Decline</Button>
      <Button type="submit">Save</Button>
    </Form>
  );
}

Parameters

export interface StorageOptions<T> {
  /**
   * The storage key name to use. This can be set to an empty string for
   * internal usage of conditionally saving items to storage.
   */
  key: string;

  /**
   * The default value to use if an item does not exist in storage.
   */
  defaultValue: UseStateInitializer<T>;

  /**
   * Set this to `true` to update:
   *
   * - the default {@link serializer} to be:
   * ```
   * typeof value === "string" ? value : `${value}`
   * ```
   * - the default {@link deserializer} to not call `JSON.parse` if the
   *   {@link defaultValue} is a string.
   *
   * @defaultValue `typeof defaultValue === 'string'`
   */
  raw?: boolean;

  /**
   * Set this to `true` to require a manual `persist()` or `remove()` call
   * to update the storage value.
   *
   * @defaultValue `false`
   */
  manual?: boolean;

  /** @defaultValue `globalThis.localStorage` */
  storage?: Storage;

  /**
   * An optional function to serialize the `value` before storing it in local
   * storage.
   *
   * @defaultValue `JSON.stringify`
   */
  serializer?: (value: T) => string;

  /**
   * An optional function to deserialize the `value` if the item existed in
   * local storage.
   *
   * @defaultValue `JSON.parse`
   */
  deserializer?: (item: string) => T;
}

Returns

export interface StorageImplementation<T> {
  value: T;

  /**
   * Updates the {@link value} in state. When the
   * {@link StorageOptions.manual} option is `false`, the value will
   * also be updated in local storage immediately.
   */
  setValue: UseStateSetter<T>;

  /**
   * Remove the item from local storage.
   */
  remove: () => void;

  /**
   * Manually persist the current {@link value} into local storage. This is only
   * useful if the {@link StorageOptions.manual} option is `true`.
   *
   * @example Manual Persisting
   * ```tsx
   * import type { ReactElement } from "react";
   *
   * function Example(): ReactElement {
   *   const { value, setValue, persist } = useStorage({
   *     key: "someKey",
   *     manual: true,
   *     defaultValue: "",
   *   });
   *
   *   return (
   *     <>
   *       <Button onClick={closeDialog}>
   *         Cancel
   *       </Button>
   *       <Button
   *         onClick={async () => {
   *           await saveToDatabase(value);
   *           persist();
   *           closeDialog();
   *         }}
   *       >
   *         Confirm
   *       </Button>
   *     </>
   *   );
   * }
   * ```
   */
  persist: () => void;
}

Utils

getItemFromStorage

function getItemFromStorage<T>(options: GetItemFromStorageOptions<T>): T;

This is a low-level helper function get a value from storage (defaults to localStorage). You'll most likely want to use a pre-built implementation like useStorage instead.

Example Usage

import { getItemFromStorage } from "@react-md/core/storage/utils";

const values = ["a", "b", "c", "d"] as const;

const item1 = getItemFromStorage({
  key: "testKey",
  fallback: values[0],
  deserializer(item) {
    if (!values.includes(item)) {
      return values[0];
    }

    return item;
  },
});

const item2 = getItemFromStorage({
  key: "anotherKey",
  fallback: -1,
});

const item3 = getItemFromStorage({
  key: "anotherKey",
  fallback: -1,
  storage: sessionStorage,
});

Parameters

export type ModifyStorageOptions = Pick<
  StorageOptions<unknown>,
  "key" | "storage"
>;

export interface GetItemFromStorageOptions<T> extends ModifyStorageOptions {
  /**
   * A value to use when the {@link key} does not exist in storage or there is
   * an error deserializing the value.
   */
  fallback: T;

  /** @see {@link StorageOptions.deserializer} */
  deserializer?: StorageDeserializer<T>;
}

Returns

The type-safe value.

setItemInStorage

function setItemInStorage<T>(options: SetItemInStorageOptions<T>): void;

You'll most likely want to use useStorage instead, but this is a low-level util to "safely" set an item in localStorage or sessionStorage.

Example Usage

import { getItemFromStorage } from "@react-md/core/storage/utils";
import { identity } from "@react-md/core/utils/identity";

const values = ["a", "b", "c", "d"] as const;

setItemInStorage({
  key: "testKey",
  value: values[0],
  // store string value as-is
  serializer: identity,
});

setItemInStorage({
  key: "anotherKey",
  value: 100,
});

setItemInStorage({
  key: "anotherKey",
  value: 100,
  storage: sessionStorage,
});

Parameters

export type ModifyStorageOptions = Pick<
  StorageOptions<unknown>,
  "key" | "storage"
>;

export interface SetItemInStorageOptions<T> extends ModifyStorageOptions {
  value: T;

  /** @see {@link StorageOptions.serializer} */
  serializer?: StorageSerializer<T>;
}

Returns

Nothing.