All files index.ts

100% Statements 64/64
92.3% Branches 12/13
100% Functions 10/10
100% Lines 43/43

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 1091x                                                   43x     43x 43x 43x 43x 43x 43x 43x 43x 43x   43x 2x 1x                         43x 12x 12x 23x 12x 12x         43x 17x 5x   17x 17x     43x 29x 8x 8x         43x 22x 5x 5x 5x       43x 17x 1x   17x     43x           8x   8x     1x 1x  
import React, { useState, useEffect, useRef } from "react";
 
export type UseStableStateProps<T> = {
  initialState: T;
  delay?: number;
  load?: () => Promise<T>;
  onStableStateChanged?: () => Promise<void>;
  onBeforeUnload?: (
    params: UseStableStateExtraParams<T>,
    event: BeforeUnloadEvent
  ) => boolean | undefined;
};
export type UseStableStateParams<T> = [
  T,
  T,
  React.Dispatch<React.SetStateAction<T>>
];
export type UseStableStateExtraParams<T> = {
  state: T;
  stableState: T;
  setState: React.Dispatch<React.SetStateAction<T>>;
  isEditing: boolean;
  delay: number;
  setDelay: React.Dispatch<React.SetStateAction<number>>;
};
 
function useStableStateExtra<T>(
  options: UseStableStateProps<T>
): UseStableStateExtraParams<T> {
  const [state, setState] = useState(options.initialState);
  const [stableState, setStableState] = useState(options.initialState);
  const lastEditTimeRef = useRef(new Date());
  const [editCount, setEditCount] = useState(0);
  const [saveTrigger, setSaveTrigger] = useState(false);
  const [isEditing, setIsEditing] = useState(false);
  const [delay, setDelay] = useState(options.delay ?? 1000);
  const isLoaded = useRef(false);
  const isInitialChange = useRef(true);
 
  window.onbeforeunload = (event) => {
    if (!options.onBeforeUnload) return;
    return options.onBeforeUnload(
      {
        state,
        stableState,
        setState,
        isEditing,
        delay,
        setDelay,
      },
      event
    );
  };
 
  useEffect(() => {
    if (!isLoaded.current) {
      (async () => {
        if (options.load) setState(await options.load());
        isInitialChange.current = true;
        isLoaded.current = true;
      })();
    }
  }, [setState]);
 
  useEffect(() => {
    if (editCount > 0) {
      setIsEditing(true);
    }
    lastEditTimeRef.current = new Date();
    setEditCount(editCount + 1);
  }, [state, setEditCount]);
 
  useEffect(() => {
    setTimeout(() => {
      if (new Date().getTime() - lastEditTimeRef.current.getTime() >= delay) {
        setSaveTrigger(true);
      }
    }, delay);
  }, [editCount, setSaveTrigger]);
 
  useEffect(() => {
    if (saveTrigger) {
      setStableState(state);
      setSaveTrigger(false);
      setIsEditing(false);
    }
  }, [saveTrigger]);
 
  useEffect(() => {
    if (!isInitialChange.current && options.onStableStateChanged) {
      options.onStableStateChanged();
    }
    isInitialChange.current = false;
  }, [stableState]);
 
  return { state, stableState, setState, isEditing, delay, setDelay } as const;
}
 
function useStableState<T>(
  options: UseStableStateProps<T>
): UseStableStateParams<T> {
  const { state, stableState, setState } = useStableStateExtra(options);
 
  return [state, stableState, setState];
}
 
export { useStableState, useStableStateExtra };
export default { useStableState, useStableStateExtra };