UNPKG

react-native-mmkv

Version:

The fastest key/value storage for React Native. ~30x faster than AsyncStorage! Works on Android, iOS and Web.

248 lines (227 loc) 7.11 kB
import { useRef, useState, useMemo, useCallback, useEffect } from 'react'; import { MMKV } from './MMKV'; import type { Configuration } from './Types'; function isConfigurationEqual( left?: Configuration, right?: Configuration ): boolean { if (left == null || right == null) return left == null && right == null; return ( left.encryptionKey === right.encryptionKey && left.id === right.id && left.path === right.path && left.mode === right.mode ); } let defaultInstance: MMKV | null = null; function getDefaultInstance(): MMKV { if (defaultInstance == null) { defaultInstance = new MMKV(); } return defaultInstance; } /** * Use the default, shared MMKV instance. */ export function useMMKV(): MMKV; /** * Use a custom MMKV instance with the given configuration. * @param configuration The configuration to initialize the MMKV instance with. Does not have to be memoized. */ export function useMMKV(configuration: Configuration): MMKV; export function useMMKV(configuration?: Configuration): MMKV { const instance = useRef<MMKV>(); const lastConfiguration = useRef<Configuration>(); if (configuration == null) return getDefaultInstance(); if ( instance.current == null || !isConfigurationEqual(lastConfiguration.current, configuration) ) { lastConfiguration.current = configuration; instance.current = new MMKV(configuration); } return instance.current; } function createMMKVHook< T extends (boolean | number | string | ArrayBufferLike) | undefined, TSet extends T | undefined, TSetAction extends TSet | ((current: T) => TSet), >(getter: (instance: MMKV, key: string) => T) { return ( key: string, instance?: MMKV ): [value: T, setValue: (value: TSetAction) => void] => { const mmkv = instance ?? getDefaultInstance(); const [bump, setBump] = useState(0); const value = useMemo(() => { // bump is here as an additional outside dependency, so this useMemo // re-computes the value each time bump changes, effectively acting as a hint // that the outside value (storage) has changed. setting bump refreshes this value. bump; return getter(mmkv, key); }, [mmkv, key, bump]); // update value by user set const set = useCallback( (v: TSetAction) => { const newValue = typeof v === 'function' ? v(getter(mmkv, key)) : v; switch (typeof newValue) { case 'number': case 'string': case 'boolean': mmkv.set(key, newValue); break; case 'undefined': mmkv.delete(key); break; case 'object': if (newValue instanceof ArrayBuffer) { mmkv.set(key, newValue); break; } else { throw new Error( `MMKV: Type object (${newValue}) is not supported!` ); } default: throw new Error(`MMKV: Type ${typeof newValue} is not supported!`); } }, [key, mmkv] ); // update value if it changes somewhere else (second hook, same key) useEffect(() => { const listener = mmkv.addOnValueChangedListener((changedKey) => { if (changedKey === key) { setBump((b) => b + 1); } }); return () => listener.remove(); }, [key, mmkv]); return [value, set]; }; } /** * Use the string value of the given `key` from the given MMKV storage instance. * * If no instance is provided, a shared default instance will be used. * * @example * ```ts * const [username, setUsername] = useMMKVString("user.name") * ``` */ export const useMMKVString = createMMKVHook((instance, key) => instance.getString(key) ); /** * Use the number value of the given `key` from the given MMKV storage instance. * * If no instance is provided, a shared default instance will be used. * * @example * ```ts * const [age, setAge] = useMMKVNumber("user.age") * ``` */ export const useMMKVNumber = createMMKVHook((instance, key) => instance.getNumber(key) ); /** * Use the boolean value of the given `key` from the given MMKV storage instance. * * If no instance is provided, a shared default instance will be used. * * @example * ```ts * const [isPremiumAccount, setIsPremiumAccount] = useMMKVBoolean("user.isPremium") * ``` */ export const useMMKVBoolean = createMMKVHook((instance, key) => instance.getBoolean(key) ); /** * Use the buffer value (unsigned 8-bit (0-255)) of the given `key` from the given MMKV storage instance. * * If no instance is provided, a shared default instance will be used. * * @example * ```ts * const [privateKey, setPrivateKey] = useMMKVBuffer("user.privateKey") * ``` */ export const useMMKVBuffer = createMMKVHook((instance, key) => instance.getBuffer(key) ); /** * Use an object value of the given `key` from the given MMKV storage instance. * * If no instance is provided, a shared default instance will be used. * * The object will be serialized using `JSON`. * * @example * ```ts * const [user, setUser] = useMMKVObject<User>("user") * ``` */ export function useMMKVObject<T>( key: string, instance?: MMKV ): [ value: T | undefined, setValue: ( value: T | undefined | ((prevValue: T | undefined) => T | undefined) ) => void, ] { const [json, setJson] = useMMKVString(key, instance); const value = useMemo(() => { if (json == null) return undefined; return JSON.parse(json) as T; }, [json]); const setValue = useCallback( (v: (T | undefined) | ((prev: T | undefined) => T | undefined)) => { if (v instanceof Function) { setJson((currentJson) => { const currentValue = currentJson != null ? (JSON.parse(currentJson) as T) : undefined; const newValue = v(currentValue); // Store the Object as a serialized Value or clear the value return newValue != null ? JSON.stringify(newValue) : undefined; }); } else { // Store the Object as a serialized Value or clear the value const newValue = v != null ? JSON.stringify(v) : undefined; setJson(newValue); } }, [setJson] ); return [value, setValue]; } /** * Listen for changes in the given MMKV storage instance. * If no instance is passed, the default instance will be used. * @param valueChangedListener The function to call whenever a value inside the storage instance changes * @param instance The instance to listen to changes to (or the default instance) * * @example * ```ts * useMMKVListener((key) => { * console.log(`Value for "${key}" changed!`) * }) * ``` */ export function useMMKVListener( valueChangedListener: (key: string) => void, instance?: MMKV ): void { const ref = useRef(valueChangedListener); ref.current = valueChangedListener; const mmkv = instance ?? getDefaultInstance(); useEffect(() => { const listener = mmkv.addOnValueChangedListener((changedKey) => { ref.current(changedKey); }); return () => listener.remove(); }, [mmkv]); }