Type safe local storage utils

Jul 29, 2025

Setter

Validate the value with the key’s Zod schema and store a JSON‑serialized version in localStorage.


export function setLocalStorageItem<K extends LocalStorageKey>(
  key: K,
  value: LocalStorageValue<K>
): void {
  try {
    const schema = LOCAL_STORAGE_SCHEMAS[key];
    const validationResult = schema.safeParse(value);
    if (!validationResult.success) {
      console.error(
        `[LocalStorageError] Type mismatch or invalid value for key "${key}". ` +
          `Provided value does not conform to its schema.`,
        validationResult.error.issues
      );
      // Depending on strictness, you might throw an error here, or just log and return.
      // For localStorage, it's often better to prevent storing bad data.
      return;
    }
    // maybe strip extra properties if the schema is strict?
    const serializedValue = JSON.stringify(validationResult.data);
    localStorage.setItem(key, serializedValue);
  } catch (error) {
    console.error(
      `[LocalStorageError] Failed to set item for key "${key}":`,
      error
    );
  }
}

Getter

Parse JSON from localStorage, validate with the key’s schema, and return the value or a safe default if parsing/validation fails.


export function getLocalStorageItem<K extends LocalStorageKey>(
  key: K,
  defaultValue?: LocalStorageValue<K>
): LocalStorageValue<K> | undefined {
  const schema = LOCAL_STORAGE_SCHEMAS[key];

  // Try to get the stored value
  const serializedValue = localStorage.getItem(key);

  // If no stored value, try to use default or schema default
  if (serializedValue === null) {
    // Validate provided default against schema
    if (defaultValue !== undefined) {
      const defaultResult = schema.safeParse(defaultValue);
      if (defaultResult.success) {
        return defaultResult.data;
      }
      console.error(
        `[LocalStorageError] Default value for key "${key}" does not conform to schema:`,
        defaultResult.error.issues
      );
    }

    // Try schema default as fallback
    const schemaDefaultResult = schema.safeParse(undefined);
    return schemaDefaultResult.success ? schemaDefaultResult.data : undefined;
  }

  let parsedValue: unknown;
  try {
    parsedValue = JSON.parse(serializedValue);
  } catch (error) {
    console.error(
      `[LocalStorageError] Failed to parse stored value for key "${key}":`,
      error
    );
    return getLocalStorageItem(key, defaultValue); // Recursively try with default
  }

  const validationResult = schema.safeParse(parsedValue);

  if (validationResult.success) {
    return validationResult.data;
  }


  console.warn(
    `[LocalStorageValidation] Stored data for key "${key}" is invalid:`,
    validationResult.error.issues
  );


  if (defaultValue !== undefined) {
    const defaultResult = schema.safeParse(defaultValue);
    if (defaultResult.success) {
      return defaultResult.data;
    }
    console.error(
      `[LocalStorageError] Default value for key "${key}" does not conform to schema:`,
      defaultResult.error.issues
    );
  }

  // Try schema default as last resort
  const schemaDefaultResult = schema.safeParse(undefined);
  return schemaDefaultResult.success ? schemaDefaultResult.data : undefined;
}
Type safe local storage utils | Yash`s website