Skip to content

Instantly share code, notes, and snippets.

@C-Sinclair
Created July 27, 2022 11:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save C-Sinclair/fe4d1db8e6c44912424ea6f04708d303 to your computer and use it in GitHub Desktop.
Save C-Sinclair/fe4d1db8e6c44912424ea6f04708d303 to your computer and use it in GitHub Desktop.
Zod typesafe localstorage hook
import { useCallback, useState } from "react";
import { z, ZodType } from "zod";
type Value<S extends ZodType> = z.infer<S>;
type ValueSetter<S extends ZodType> = (currentValue: Value<S>) => void;
type UseZodls<S extends ZodType> = [
/**
* The current value of the data from local storage
* Will default to the defaultValue if not found in local storage
*/
value: Value<S>,
/**
* Updates localStorage with the new value
* @throws if the provided value does not pass the type check
*/
set: (value: Value<S> | ValueSetter<S>) => void
];
/**
* React hook enabling Typesafe access to localStorage
*
* @param key {string} -- the localStorage string key of where to find the data
* @param schema -- the Zod schema to ensure the data matches
* @param defaultValue -- the default object to return on error or missing data
*
* @example
* ```tsx
* const schema = z.object({
* name: z.string(),
* })
* const [value, setValue] = useZodls("keytostore", schema, { name: "default" })
* value // { name: "default" }
* setValue({ name: "newName" })
* value // { name: "newName" }
* ```
*/
export function useZodls<S extends ZodType>(
key: string,
schema: S,
defaultValue: Value<S>
): UseZodls<S> {
const get = useCallback(() => {
try {
const stored = JSON.parse(localStorage.getItem(key));
const parsed = schema.safeParse(stored);
if (parsed.success) {
return parsed.data;
}
return defaultValue;
} catch (e) {
// if the JSON.parse fails we still want to return the defaultValue
return defaultValue;
}
}, [key, schema, defaultValue]);
const [value, setValue] = useState<Value<S> | undefined>(get);
const set = useCallback(
(value: Value<S> | ValueSetter<S>) => {
let newValue: Value<S> = value;
if (typeof value === "function") {
const valueSetter = value as ValueSetter<S>;
const current = get();
newValue = valueSetter(current);
}
// might throw
schema.parse(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
setValue(newValue);
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- ESlint wants `S` to be a dependency, but its a type?!!
[setValue, get, schema, key]
);
return [value, set];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment