UNPKG

react-rv

Version:

react-rv is a lightweight and efficient state management library for React that allows you to create reactive variables and subscribe to them with minimal overhead.

1 lines 7.6 kB
{"version":3,"sources":["../src/index.ts","../src/rv.ts","../src/use-rv.ts","../src/use-rv-effect.ts"],"sourcesContent":["// Copyright (c) rasulomaroff. All rights reserved. Licensed under the MIT license.\n\nexport { rv } from './rv'\nexport { useRv } from './use-rv'\nexport { useRvEffect } from './use-rv-effect'\n\nexport type * from './types'\n","import type { CleanupFn, EqualFn, Listener, Rv, RvInitOptions, RvOptions } from './types'\n\nconst defaultEq = <T>(oldValue: T, newValue: T): boolean => oldValue === newValue\n\n/**\n * Creates a reactive variable (RV), allowing value retrieval, updates, and subscriptions.\n *\n * @template T The type of the stored value.\n *\n * @param val The initial value of the reactive variable.\n * @param opts Optional configuration for the reactive variable.\n *\n * @returns A reactive variable function that allows getting, setting, and listening for updates.\n *\n * @example\n * ```ts\n * // initialize a state variable with initial value set to 0\n * const positiveVar = rv(0, {\n * // all options are optional\n *\n * // define custom `eq` function that will be run on every value set to determine\n * // whether or not value is going to be updated\n * eq: (oldValue, newValue) => newValue > oldValue && newValue >= 0,\n *\n * // define callback that's going to be run on every change\n * on: (newValue, oldValue) => {}\n * })\n *\n * // alternatively, there's a handy function initializer\n * // all options are identical\n * const positiveVar = rv.fn(() => 0)\n *\n * // call variable function with no arguments to get its current value\n * const currentValue = positiveVar()\n *\n * // call variable function passing an argument in order to set it\n * // Won't trigger an update because the values are \"equal\" under this custom rule.\n * positiveVar(-3, {\n * // override the initial `eq` function for this update call.\n * eq: (oldValue, newValue) => newValue > oldValue\n * // additionally, you can disable initial `eq` function by passing `false` here\n * // it will use a default `eq` function which is just a strict check: `===`\n * eq: false // in this case, update WILL happen\n * })\n *\n * // you can also subscribe to value without using any hooks\n * const unsubscribe = positiveVar.on((newValue, oldValue) => console.log(newValue, oldValue))\n *\n * positiveVar(4) // logs: 4\n *\n * unsubscribe()\n *\n * positiveVar(5) // there will be no logs\n * ```\n */\nexport function rv<T>(val: T, opts?: RvInitOptions<T>): Rv<T> {\n const { eq = defaultEq, on } = opts ?? <RvInitOptions<T>>{}\n\n const listeners = new Set<Listener<T>>()\n\n if (on) listeners.add(on)\n\n const fn: Rv<T> = (...args: [] | [newValue: T, opts?: RvOptions<T>]): T => {\n if (args.length === 0) return val\n\n const [newValue, opts] = args\n\n let eqfn: EqualFn<T> = eq\n\n if (typeof opts?.eq !== 'undefined') {\n eqfn = opts.eq === false ? defaultEq : opts.eq\n }\n\n if (eqfn(val, newValue)) return val\n\n const oldValue = val\n val = newValue\n\n listeners.forEach(cb => cb(newValue, oldValue))\n\n return val\n }\n\n fn.off = (f): void => void listeners.delete(f)\n fn.size = (): number => listeners.size\n\n fn.on = (listener): CleanupFn => {\n listeners.add(listener)\n\n return () => {\n listeners.delete(listener)\n }\n }\n\n return fn\n}\n\n/**\n * Creates a reactive variable from an initializer function.\n * The function is immediately executed to determine the initial value.\n *\n * @template T The type of the stored value.\n *\n * @param init A function that returns the initial value.\n * @param options Optional configuration for equality comparison and event listeners.\n *\n * @returns A reactive variable function.\n */\n// eslint-disable-next-line @typescript-eslint/explicit-function-return-type\nrv.fn = <T>(init: () => T, options?: RvInitOptions<T>) => rv(init(), options)\n\nexport namespace rv {\n /**\n * Infers the type of the reactive variable.\n *\n * @example\n * ```ts\n * const darkMode = rv(false)\n *\n * type DarkMode = rv.infer<typeof darkMode> // infers as boolean\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n export type infer<T extends Rv<any>> = T extends Rv<infer X> ? X : never\n}\n","// using this package to support all react versions starting from 16.8 where hooks were just introduced\nimport { useSyncExternalStore } from 'use-sync-external-store/shim'\n\nimport type { Rv } from './types'\n\n/**\n * Subscribes to a reactive variable and provides its current value,\n * updating the state when the variable is updated.\n *\n * @template T The type of the reactive variable's value.\n * @param rv The reactive variable to subscribe to.\n *\n * @returns The current value of the reactive variable.\n *\n * @example\n * ```tsx\n * import React from 'react'\n * import { rv, useRv } from 'react-rv'\n *\n * const counter = rv(0)\n *\n * const Counter = () => {\n * const value = useRv(counter)\n * return (\n * <div>\n * <p>Count: {value}</p>\n * <button onClick={() => counter(value + 1)}>Increment</button>\n * </div>\n * )\n * }\n * ```\n */\nexport const useRv = <T>(rv: Rv<T>): T => useSyncExternalStore(rv.on, rv, rv)\n","import { useEffect, useRef } from 'react'\nimport type { Listener, Rv } from './types'\n\n/**\n * React hook to subscribe to updates of a reactive variable.\n *\n * Note: callback is always using current scope.\n *\n * @example\n * ```ts\n * const counter = rv(0)\n *\n * useRvEffect(counter, (newVal, oldVal) => {\n * console.table({ newVal, oldVal })\n * })\n * ```\n *\n * @param rv - The reactive variable to subscribe to.\n * @param f - A callback function triggered whenever the reactive variable is updated.\n * The first and second arguments are the new and the old value of this `rv`.\n */\nexport function useRvEffect<T>(rv: Rv<T>, f: Listener<T>): void {\n const fnRef = useRef(f)\n fnRef.current = f\n\n useEffect(() => rv.on((...args) => fnRef.current(...args)), [rv])\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,QAAAE,EAAA,UAAAC,EAAA,gBAAAC,IAAA,eAAAC,EAAAL,GCEA,IAAMM,EAAY,CAAIC,EAAaC,IAAyBD,IAAaC,EAqDlE,SAASC,EAAMC,EAAQC,EAAgC,CAC1D,GAAM,CAAE,GAAAC,EAAKN,EAAW,GAAAO,CAAG,EAAIF,GAA0B,CAAC,EAEpDG,EAAY,IAAI,IAElBD,GAAIC,EAAU,IAAID,CAAE,EAExB,IAAME,EAAY,IAAIC,IAAqD,CACvE,GAAIA,EAAK,SAAW,EAAG,OAAON,EAE9B,GAAM,CAACF,EAAUG,CAAI,EAAIK,EAErBC,EAAmBL,EAMvB,GAJI,OAAOD,GAAM,GAAO,MACpBM,EAAON,EAAK,KAAO,GAAQL,EAAYK,EAAK,IAG5CM,EAAKP,EAAKF,CAAQ,EAAG,OAAOE,EAEhC,IAAMH,EAAWG,EACjB,OAAAA,EAAMF,EAENM,EAAU,QAAQI,GAAMA,EAAGV,EAAUD,CAAQ,CAAC,EAEvCG,CACX,EAEA,OAAAK,EAAG,IAAOI,GAAY,KAAKL,EAAU,OAAOK,CAAC,EAC7CJ,EAAG,KAAO,IAAcD,EAAU,KAElCC,EAAG,GAAMK,IACLN,EAAU,IAAIM,CAAQ,EAEf,IAAM,CACTN,EAAU,OAAOM,CAAQ,CAC7B,GAGGL,CACX,CAcAN,EAAG,GAAK,CAAIY,EAAeC,IAA+Bb,EAAGY,EAAK,EAAGC,CAAO,EC5G5E,IAAAC,EAAqC,wCA+BxBC,EAAYC,MAAiB,wBAAqBA,EAAG,GAAIA,EAAIA,CAAE,EChC5E,IAAAC,EAAkC,iBAqB3B,SAASC,EAAeC,EAAWC,EAAsB,CAC5D,IAAMC,KAAQ,UAAOD,CAAC,EACtBC,EAAM,QAAUD,KAEhB,aAAU,IAAMD,EAAG,GAAG,IAAIG,IAASD,EAAM,QAAQ,GAAGC,CAAI,CAAC,EAAG,CAACH,CAAE,CAAC,CACpE","names":["index_exports","__export","rv","useRv","useRvEffect","__toCommonJS","defaultEq","oldValue","newValue","rv","val","opts","eq","on","listeners","fn","args","eqfn","cb","f","listener","init","options","import_shim","useRv","rv","import_react","useRvEffect","rv","f","fnRef","args"]}