@yanfoo/react-dict

Reactive dictionaries for React

Usage no npm install needed!

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

README

Reactive Dictionaries

Observable dictionaries for React. For observable values for React, take a look at @yanfoo/react-var.

npm (scoped)

Install

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

Usage

import React, { useCallback } from 'react';
import { createReactDict, useReactDict } from '@yanfoo/react-dict';

// default comparator is equality : a === b
const foodPreferences = createReactDict(null, {
   comparator: (a, b) => JSON.stringify(a) === JSON.stringify(b)
});

const FoodPreferenceFruit = () => {
   const { apple:appleSelected, banana:bananaSelected, orange:orangeSelected } = 
      useReactDict(foodPreferences, [ 'apple', 'banana', 'orange' ]);

   const handleClick = useCallback(e => foodPreferences({ 
      [e.target.value]: selected => !selected
   }), []);

   return (
      <div>
         <div>
            <input type="checkbox" id="checkFood1" value="apple" onClick={ handleClick } />
            <label for="checkFood1"> I love apples</label>
         </div>
         <div>
            <input type="checkbox" id="checkFood2" value="banana" onClick={ handleClick } />
            <label for="checkFood2"> I love bananas</label>
         </div>
         <div>
            <input type="checkbox" id="checkFood3" value="orange" onClick={ handleClick } />
            <label for="checkFood3"> I love oranges</label>
         </div>
      </div>
   );
};

const FoodPreferenceVegetable = () => {
   const { carrot:carrotSelected, brocoli:brocoliSelected, celeri:celeriSelected } = 
      useReactDict(foodPreferences, [ 'carrot', 'brocoli', 'celeri' ]);

   const handleClick = useCallback(e => foodPreferences({
      [e.target.value]: selected => !selected 
   }, []);

   return (
      <div>
         <div>
            <input type="checkbox" id="checkFood1" value="carrot" onClick={ handleClick } />
            <label for="checkFood1"> I love carrots</label>
         </div>
         <div>
            <input type="checkbox" id="checkFood2" value="brocoli" onClick={ handleClick } />
            <label for="checkFood2"> I love brocolis</label>
         </div>
         <div>
            <input type="checkbox" id="checkFood3" value="celeri" onClick={ handleClick } />
            <label for="checkFood3"> I love celeris</label>
         </div>
      </div>
   );
};

API


declare type ReactDictChangeEvent<T> = {
   /**
    * The keys that have been updated
    */
   updatedKeys: Array<string>,
   /**
    * The current handled values (including the keys that haven't updated)
    */
   values(): Record<string,T>
}


export type ReactDict<T> = {
   /**
    * Set a new value and notify subscribers
    */
   (newValue?: T | ((value: T) => T)): Promise<ReactDict<T>>;
   /**
    * Get a 
    */
   dictionary: Record<string,T>
   /**
    * Get the current values for the specified keys
    */
   values(keys: Iterable<string>): Record<string,T>;
   /**
    * Register a new subscriber. Returns a function to unsubsribe the handler
    * @param handler will be called with the updated value
    */
   subscribe(handler: (event: ReactDictChangeEvent<T>) => Promise<void> | void): () => void;
   /**
    * Unregister a subscriber
    * @param handler must be the exact same subscribed handler
    */
   unsubscribe(handler: (event: ReactDictChangeEvent<T>) => Promise<void> | void): void;
};

export type ReactDictOptions<T> = {
   comparator: (newVal: T, oldVal: T) => boolean,
   transform: (newVal: T, oldVal: T, changed: boolean, key: string) => Promise<T> | T
};

export function createReactDict<T>(initialValue: T, options:ReactDictOptions<T>): ReactDict<T>;

/**
 * Use the specified dictionary, subscribing to only the specified keys
 * @param reactDict 
 * @param keys the list of keys to subscribe reactively
 */
export function useReactDict<T>(reactDict: ReactDict<T>, keys: Iterable<string>): Record<string,T>

Contribution

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

License

MIT