@yanfoo/react-var

Reactive variables for React

Usage no npm install needed!

<script type="module">
  import yanfooReactVar from 'https://cdn.skypack.dev/@yanfoo/react-var';
</script>

README

Reactive variables

Observable values for React. For observable dictionaries for React, take a look at @yanfoo/react-dict. This project was inspired by jorbuedo's react-reactive-var.

npm (scoped)

Breaking changes

  • 1.2.0 > 1.3.0 - removed ReactVar.isLocked and ReactVar.wait() in favor of sequential modifications.

Install

npm install @yanfoo/react-var --save
yarn add @yanfoo/react-var

Usage

import { createReactVar, useReactVar } from '@yanfoo/react-var';

// default comparator is equality : a === b
const activeUser = createReactVar(null, { comparator: (a, b) => a?.id === b?.id });

export const authenticator = {
   login: (username, password) => {
      activeUser({ id: 123, username });
   },
   logout: () => {
      activeUser(null);
   }
};

export const useAuthenticatedUser = () => useReactVar(activeUser);

Limitations

Setting a new values to a ReactVar can be made asynchronously. Meaning that any modification will cause parallel modifications to be stacked and processed sequentially. However, trying to modify a ReactVar within a subscriber callback will fail, and this is in order to prevent race conditions, where the subscriber and the setter would wait for each other to complete, creating a dead lock situation.

API

declare type ReactVarChangeEvent<T> = {
   /**
    * The current value
    */
   value: T
}

type ReactVar<T> = {
   /**
    * Set a new value and optionally notify subscribers. If the new value
    * is a function, or an asynchronous function, it will be called asynchronously
    * with the current value as argument.
    */
   (newValue?: T | ((value: T) => T) | ((value: T) => Promise<T>)): Promise<ReactVar<T>>;
   /**
    * Get the current value
    */
   readonly value: T;
   /**
    * Register a new subscriber. Returns a function to unsubsribe the handler
    * @param handler will be called with the updated value
    */
   subscribe(handler: (event: ReactVarChangeEvent<T>) => Promise<void> | void): () => void;
   /**
    * Unregister a subscriber
    * @param handler must be the exact same subscribed handler
    */
   unsubscribe(handler: (event: ReactVarChangeEvent<T>) => Promise<void> | void): void;
}

type ReactVarOptions<T> = {
   /**
    * Test the equality of two values. If the comparator returns false,
    * then the subscribers will be notified with the changed value. If
    * the compartor returns true, then the value is considered equal,
    * and subscribers are not notified about any change.
    * 
    * This option controls whether or not subscribers get notified on
    * changes. Do not directly modify newVal or oldVal, use the transform
    * function option for that.
    * 
    * Default comparator is : (newVal, oldVal) => newVal === oldVal
    * 
    * @param newVal
    * @param oldVal
    */
   comparator: (newVal: T, oldVal: T) => bool
   /**
    * Intercept the value as it's being set. This function is exected after
    * the comparator, therefore newVal equals the value tested against oldVal.
    * 
    * By default, the value is always updated, regardless if the comaprator
    * considers them equal or not. To change this behavior, return oldVal if changed
    * is false so that the old value is preserved.
    * 
    * This option controls what ReactVar.value returns, if the comparator function
    * returns true, subscribers will still be notified regardless whether newVal or oldVal
    * is returned. See the comparator function optionn.
    * 
    * Whatever is returned by this function will be returned by ReactVar.value.
    * 
    * Default tranform is : newVal => newVal
    * 
    * @param newVal
    * @param oldVal
    * @param changed the comparator result
    */
   transform: (newVal: T, oldVal: T, changed: bool) => Promise<T> | T
}

/**
 * Create a new reactive variable, then return it
 */
const createReactVar<T>(initialValue: T, options: ReactVarOptions<T>): ReactVar<T>;

/**
 * React hook that will subscribe to the specified reactive variable
 */
const useReactVar<T>(reactVar: ReactVar<T>): T;

Contribution

All contributions welcome! Every PR must be accompanied by their associated unit tests!

License

MIT