All files / src useMappedState.ts

92.11% Statements 35/38
72.73% Branches 8/11
85.71% Functions 12/14
94.12% Lines 32/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95                      9x                                       45x 45x 35x   10x 4x     6x     45x 45x 45x   45x   21x 22x           22x         45x 105x 96x     45x 29x 29x 24x 24x       45x 45x       45x 21x 29x   21x         45x 22x 22x     45x    
import * as React from 'react';
import { StateGetter } from './types';
import { Store } from './Store';
import { objectIs } from './utils';
 
type TupleOfStateGetter = [] | [StateGetter<any>, ...StateGetter<any>[]];
type ExtractState<T> = T extends StateGetter<any>[]
  ? { [P in keyof T]: T[P] extends StateGetter<infer S> ? S : never }
  : never;
 
type EqualityFn = (a: unknown, b: unknown) => boolean;
const defaultEquality = objectIs;
 
export function useMappedState<T extends TupleOfStateGetter, R>(
  stateGetters: T,
  mapperFn: (...args: ExtractState<T>) => R,
  deps?: any[]
): R;
export function useMappedState<T extends TupleOfStateGetter, R>(
  stateGetters: T,
  mapperFn: (...args: ExtractState<T>) => R,
  equalityFn: EqualityFn,
  deps?: any[]
): R;
 
export function useMappedState(
  stateGetters: StateGetter<any>[],
  mapperFn: (...args: any[]) => any,
  equalityFnOrDeps?: EqualityFn | unknown[],
  mayBeDeps: unknown[] = []
) {
  const parseArgs = (): [unknown[], EqualityFn] => {
    if (equalityFnOrDeps === undefined) {
      return [[], defaultEquality];
    }
    if (Array.isArray(equalityFnOrDeps)) {
      return [equalityFnOrDeps, defaultEquality];
    }
 
    return [mayBeDeps, equalityFnOrDeps];
  };
 
  const [deps, equalityFn] = parseArgs();
  const mapperCached = React.useCallback(mapperFn, deps);
  const [, forceUpdate] = React.useReducer(x => x + 1, 0);
 
  const stores = React.useMemo(
    () =>
      stateGetters.map((getter: any) => {
        Iif (!getter._store) {
          throw new Error(
            `_store not found in getter for module "${getter._module ||
              'unknown'}". Make sure to load the module before using 'useState' or 'useMappedState'.`
          );
        }
        return getter._store as Store;
      }),
    []
  );
 
  const getMappedState = () => {
    const states = stores.map(store => store.state);
    return mapperCached(...states);
  };
 
  const getSubscribeFn = () => {
    const newState = getMappedState();
    if (!equalityFn(newState, stateRef.current)) {
      stateRef.current = newState;
      forceUpdate({});
    }
  };
 
  const stateRef = React.useRef(getMappedState());
  const subscribeRef = React.useRef(getSubscribeFn);
 
  // subscribe to stored immediately
  // React.useEffect can sometimes miss updates
  React.useLayoutEffect(() => {
    const subscriptions = stores.map(store =>
      store.subscribe(() => subscribeRef.current())
    );
    return () => {
      subscriptions.forEach(subscription => subscription());
    };
  }, []);
 
  React.useMemo(() => {
    stateRef.current = getMappedState();
    subscribeRef.current = getSubscribeFn;
  }, deps);
 
  return stateRef.current;
}