All files index.ts

100% Statements 46/46
96% Branches 24/25
100% Functions 12/12
100% Lines 40/40

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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134                                                                            10x     1x                 17x   17x   24x 24x 22x 22x       25x   17x       21x 7x   21x     20x       20x 20x 19x 10x     2x 2x     21x 21x     17x   17x       32x 32x                   32x 13x 11x 4x       28x 28x 28x 28x     28x   28x   28x     17x 17x   17x    
import { useEffect, useLayoutEffect, useReducer, useRef } from 'react'
 
export type State = Record<string | number | symbol, any>
export type StateListener<T> = (state: T) => void
export type StateSelector<T extends State, U> = (state: T) => U
export type PartialState<T extends State> =
  | Partial<T>
  | ((state: T) => Partial<T>)
export type EqualityChecker<T> = (state: T, newState: any) => boolean
export interface SubscribeOptions<T extends State, U> {
  selector?: StateSelector<T, U>
  equalityFn?: EqualityChecker<U>
  currentSlice?: U
  subscribeError?: Error
}
export type StateCreator<T extends State> = (
  set: SetState<T>,
  get: GetState<T>,
  api: StoreApi<T>
) => T
export type SetState<T extends State> = (partial: PartialState<T>) => void
export type GetState<T extends State> = () => T
export type Subscribe<T extends State> = <U>(
  listener: StateListener<U | void>,
  options?: SubscribeOptions<T, U>
) => () => void
export type Destroy = () => void
export type UseStore<T extends State> = <U>(
  selector?: StateSelector<T, U>,
  equalityFn?: EqualityChecker<U>
) => U
export interface StoreApi<T extends State> {
  setState: SetState<T>
  getState: GetState<T>
  subscribe: Subscribe<T>
  destroy: Destroy
}
 
const forceUpdateReducer = (state: boolean) => !state
// For server-side rendering: https://github.com/react-spring/zustand/pull/34
const useIsoLayoutEffect =
  typeof window === 'undefined' ? useEffect : useLayoutEffect
 
export default function create<TState extends State>(
  // Use TState for createState signature when available.
  // e.g. create<MyState>(set => ...
  createState: keyof TState extends never
    ? StateCreator<State>
    : StateCreator<TState>
): [UseStore<TState>, StoreApi<TState>] {
  const listeners: Set<StateListener<void>> = new Set()
 
  const setState: SetState<TState> = partial => {
    const partialState =
      typeof partial === 'function' ? partial(state) : partial
    if (partialState !== state) {
      state = Object.assign({}, state, partialState)
      listeners.forEach(listener => listener())
    }
  }
 
  const getState: GetState<TState> = () => state
 
  const subscribe: Subscribe<TState> = <StateSlice>(
    listener: StateListener<StateSlice | void>,
    options: SubscribeOptions<TState, StateSlice> = {}
  ) => {
    if (!('currentSlice' in options)) {
      options.currentSlice = (options.selector || getState)(state)
    }
    const listenerFn = () => {
      // Destructure in the listener to get current values. We rely on this
      // because options is mutated in useStore.
      const { selector = getState, equalityFn = Object.is } = options
      // Selector or equality function could throw but we don't want to stop
      // the listener from being called.
      // https://github.com/react-spring/zustand/pull/37
      try {
        const newStateSlice = selector(state)
        if (!equalityFn(options.currentSlice as StateSlice, newStateSlice)) {
          listener((options.currentSlice = newStateSlice))
        }
      } catch (error) {
        options.subscribeError = error
        listener()
      }
    }
    listeners.add(listenerFn)
    return () => void listeners.delete(listenerFn)
  }
 
  const destroy: Destroy = () => listeners.clear()
 
  const useStore = <StateSlice>(
    selector: StateSelector<TState, StateSlice> = getState,
    equalityFn: EqualityChecker<StateSlice> = Object.is
  ): StateSlice => {
    const isInitial = useRef(true)
    const options = useRef(
      // isInitial prevents the selector from being called every render.
      isInitial.current && {
        selector,
        equalityFn,
        currentSlice: ((isInitial.current = false), selector(state)),
      }
    ).current as SubscribeOptions<TState, StateSlice>
 
    // Update state slice if selector has changed or subscriber errored.
    if (selector !== options.selector || options.subscribeError) {
      const newStateSlice = selector(state)
      if (!equalityFn(options.currentSlice as StateSlice, newStateSlice)) {
        options.currentSlice = newStateSlice
      }
    }
 
    useIsoLayoutEffect(() => {
      options.selector = selector
      options.equalityFn = equalityFn
      options.subscribeError = undefined
    })
 
    const forceUpdate = useReducer(forceUpdateReducer, false)[1]
 
    useIsoLayoutEffect(() => subscribe(forceUpdate, options), [])
 
    return options.currentSlice as StateSlice
  }
 
  const api = { setState, getState, subscribe, destroy }
  let state = createState(setState, getState, api)
 
  return [useStore, api]
}