summaryrefslogtreecommitdiffhomepage
path: root/desktop/packages/mullvad-vpn/src/renderer/lib/utility-hooks.ts
blob: 0fe9a9314e15a8f87dcc65259fb2b38c3bd99794 (plain)
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
import React, { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';

export function useMounted() {
  const mountedRef = useRef(false);
  const isMounted = useCallback(() => mountedRef.current, []);

  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  }, []);

  return isMounted;
}

export function useStyledRef<T>(): React.RefObject<T | null> {
  return useRef<T>(null);
}

export function useCombinedRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {
  return useRefCallback((element: T | null) => refs.forEach((ref) => assignToRef(element, ref)));
}

export function assignToRef<T>(element: T | null, ref?: React.Ref<T>) {
  if (typeof ref === 'function') {
    ref(element);
  } else if (ref && element) {
    (ref as React.RefObject<T>).current = element;
  }
}

export function useBoolean(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);
  const toggle = useCallback(() => setValue((value) => !value), []);

  return [value, setTrue, setFalse, toggle] as const;
}

// This hook returns a function that can be used to force a rerender of a component, and
// additionally also returns a variable that can be used to trigger effects as a result. This is a
// hack and should be avoided unless there are no better ways.
export function useRerenderer(): [() => void, number] {
  const [count, setCount] = useState(0);
  const rerender = useCallback(() => setCount((count) => count + 1), []);
  return [rerender, count];
}

type Fn<T extends unknown[], R> = (...args: T) => R;

export function useEffectEvent<Args extends unknown[]>(
  fn: Fn<Args, void | undefined | Promise<void | undefined>>,
): Fn<Args, void> {
  const ref = useRef<Fn<Args, void>>(fn);

  useInsertionEffect(() => {
    ref.current = fn;
  }, [fn]);

  return useCallback((...args: Args) => ref.current(...args), []);
}

// Alias for useEffectEvent, but with another name since the effect event is named after a very
// specific usecase.
export const useRefCallback = useEffectEvent;

export function useLastDefinedValue<T>(value: T): T {
  const [definedValue, setDefinedValue] = useState(value);

  useEffect(() => setDefinedValue((prev) => value ?? prev), [value]);

  return value ?? definedValue;
}