react-app/src/contexts/snackbarContext.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import React from 'react';
import {
  ReactNode,
  SyntheticEvent,
  createContext,
  useState,
  useMemo,
  Dispatch,
  SetStateAction,
  CSSProperties,
} from 'react';
import Snackbar from '@mui/material/Snackbar';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import { SnackbarContent, useTheme } from '@mui/material';

/**
 * @interface
 * @description Properties passed to SnackbarContext.
 * @property {ReactNode} children The child elements within the SnackbarContext.
 */
interface ISnackBarContext {
  children: ReactNode;
}

/**
 * @interface
 * @description Defines the properties of an Message State.
 * @property {string} text The text displayed in the notification.
 * @property {boolean} open Whether the notification is open and visible.
 * @property {CSSProperties} style Optional: Styling properties for the notification.
 */
interface MessageState {
  text: string;
  open: boolean;
  style?: CSSProperties;
}

/**
 * @constant
 * @description The initial state of the component. MessageState
 */
const initialState: MessageState = {
  text: '',
  open: false,
};

/**
 * @constant
 * @description The initial context passed down from the context provider.
 */
const initialContext = {
  messageState: initialState,
  setMessageState: (() => {}) as Dispatch<SetStateAction<MessageState>>,
  styles: {
    success: {},
    warning: {},
  },
};
/**
 * @constant
 * @description The context provided by the SnackbarContext.
 */
export const SnackBarContext = createContext(initialContext);

/**
 * @description Wraps the application and provides a popup notification that can be used with the supplied SnackBarContext.
 * @param {ISnackBarContext} props Properties passed to the component.
 * @returns A React component
 */
const SnackBarContextProvider = (props: ISnackBarContext) => {
  const theme = useTheme();
  const snackbarStyles = {
    success: { backgroundColor: theme.palette.success.main },
    warning: { backgroundColor: theme.palette.warning.main },
  };
  const [messageState, setMessageState] = useState(initialState);
  // Value passed into context later
  const value = useMemo(
    () => ({
      messageState: messageState,
      setMessageState: setMessageState,
      styles: snackbarStyles,
    }),
    [messageState],
  );

  const { children } = props;

  // When the closing X is clicked.
  const handleClose = (event: SyntheticEvent | Event, reason?: string) => {
    if (reason === 'clickaway') {
      return;
    }
    setMessageState({
      ...messageState, //Spread to keep correct style even while fading out.
      text: '',
      open: false,
    });
  };

  // The X element in the notification.
  const action = (
    <IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
      <CloseIcon fontSize="small" />
    </IconButton>
  );

  return (
    <SnackBarContext.Provider value={value}>
      {children}
      <Snackbar open={messageState.open} autoHideDuration={6000} onClose={handleClose}>
        <SnackbarContent
          sx={messageState.style || snackbarStyles.warning}
          message={messageState.text}
          action={action}
        />
      </Snackbar>
    </SnackBarContext.Provider>
  );
};

export default SnackBarContextProvider;