Passed
Push — feature/data-hook-error-toast ( 1a9769...5e187f )
by Tristan
05:11
created

resources/assets/js/components/ErrorToast.tsx   A

Complexity

Total Complexity 4
Complexity/F 4

Size

Lines of Code 84
Function Count 1

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 4
eloc 62
mnd 3
bc 3
fnc 1
dl 0
loc 84
rs 10
bpm 3
cpm 4
noi 0
c 0
b 0
f 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A ErrorToast.tsx ➔ runCallbackIfValueUnchanged 0 16 2
1
import React, { useCallback, useContext, useEffect, useRef } from "react";
2
import { defineMessages, useIntl } from "react-intl";
3
import { ErrorContext } from "./ErrorContainer";
4
import Alert from "./H2Components/Alert";
5
6
const TOAST_SELF_DISMISS_TIMER = 3500;
7
8
export const errorMessages = defineMessages({
9
  toastTitle: {
10
    id: "errorToast.title",
11
    defaultMessage: "Something went wrong!",
12
    description: "Title displayed on the Error Toast component.",
13
  },
14
});
15
16
/**
17
 * Creates a closure which can be called in the future, to conditionally run a callback only if a certain value is unchanged.
18
 *
19
 * @param pastValue This value is fixed when the closure is initially created.
20
 * @param futureValueRef This allows access to a variable which may be changed by the time this closure runs.
21
 * @param callback This function will be called if pastValue === futureValueRef.current when this closure runs.
22
 */
23
function runCallbackIfValueUnchanged<T>(
24
  pastValue: T,
25
  futureValueRef: React.MutableRefObject<T>,
26
  callback: () => void,
27
) {
28
  return () => {
29
    if (pastValue === futureValueRef.current) {
30
      callback();
31
    }
32
  };
33
}
34
35
export const ErrorToast: React.FC = () => {
36
  const intl = useIntl();
37
  const { state, dispatch } = useContext(ErrorContext);
38
  const dismiss = useCallback(() => dispatch({ type: "pop" }), [dispatch]);
39
40
  // This toast will render the first error in the queue, if any.
41
  const currentError = state.errorQueue.length > 0 ? state.errorQueue[0] : null;
42
43
  // This ref object makes the currently rendered error available to an async closure, dismissCurrentError
44
  const currentErrorRef = useRef(currentError);
45
  currentErrorRef.current = currentError;
46
47
  useEffect(() => {
48
    if (currentError !== null) {
49
      // At the end of the timeout, if the current error hasn't been manually dismissed, dismiss it.
50
      const dismissCurrentError = runCallbackIfValueUnchanged(
51
        currentError,
52
        currentErrorRef,
53
        dismiss,
54
      );
55
      setTimeout(dismissCurrentError, TOAST_SELF_DISMISS_TIMER);
56
    }
57
  }, [currentError, dismiss]);
58
59
  return (
60
    <>
61
      {currentError !== null && (
62
        <Alert
63
          color="stop"
64
          position="toast"
65
          data-h2-radius="b(round)"
66
          data-h2-padding="b(all, .25)"
67
          dismissBtn={
68
            <Alert.DismissBtn onClick={dismiss}>
69
              <i className="fa fa-times-circle" />
70
            </Alert.DismissBtn>
71
          }
72
        >
73
          <Alert.Title>
74
            <strong>{intl.formatMessage(errorMessages.toastTitle)}</strong>
75
          </Alert.Title>
76
          <p>{currentError}</p>
77
        </Alert>
78
      )}
79
    </>
80
  );
81
};
82
83
export default ErrorToast;
84