# All files / src/components data-list.tsx

80% Statements 12/15
20% Branches 1/5
100% Functions 4/4
80% Lines 12/15

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

    8x

    40x
    40x

    40x

    8x

    8x

    9x

    9x

    90x
    90x

    8x

    6x
     // Following exception used because of false positives on linting rule:
    // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md#false-positives-sfc
    /* eslint-disable react/no-unused-prop-types */

    import { memo, type ReactNode, type HTMLAttributes } from 'react';
    import cn from 'classnames';

    import Loader from './loader';

    import useDataCheckboxes from '../hooks/useDataCheckboxes';

    import withDataLoader, { type WrapperProps } from './data-loader';

    export type Props<Datum> = {
      /**
       * The data to be displayed
       */
      data: Datum[];
      /**
       * Flag saying that data is loading, so we might be showing stale data
       */
      loading?: boolean;
      /**
       * A function that returns a unique ID for each of the data objects.
       * Same function signature as a map function.
       */
      getIdKey: (datum: Datum, index: number, data: Datum[]) => string;
      /**
       * A callback that is called whenever a user selects or unselects a row.
       */
      onSelectionChange?: (event: MouseEvent | KeyboardEvent) => void;
      /**
       * A renderer function for each item of the list.
       * Make sure that it doesn't change unecessarily by wrapping it in useCallback
       */
      dataRenderer: (datum: Datum) => ReactNode;
    };

    type BasicDatum = Record<string, unknown>;

    type DataListItemProps<Datum> = {
      datum: Datum;
      id: string;
      dataRenderer: (datum: Datum) => ReactNode;
      loading: boolean;
      firstItem: boolean;
    };

    const DataListItem = <Datum extends BasicDatum>({
      datum,
      id,
      dataRenderer,
      loading,
      firstItem,
    }: DataListItemProps<Datum>) => {
      let rendered: ReactNode;
      try {
        rendered = dataRenderer(datum);
      } catch (error) {
        /**
         * We get here only if the renderer fails. If the renderer returns null of
         * undefined because of a lack of data, then it will no throw and will not
         * display the loader at all
         */
        if (!loading) {
          throw error;
        } else {
          rendered = firstItem && <Loader />;
        }
      }

      return <li key={id}>{rendered}</li>;
    };
    const MemoizedDataListItem = memo(DataListItem) as typeof DataListItem;

    export const DataList = <Datum extends BasicDatum>({
      data,
      getIdKey,
      dataRenderer,
      loading = false,
      onSelectionChange,
      className,
      ...props
    }: Props<Datum> & HTMLAttributes<HTMLUListElement>) => {
      const { checkboxContainerRef } = useDataCheckboxes(onSelectionChange);

      return (
        <ul
          {...props}
          className={cn('data-list', 'no-bullet', className)}
          ref={checkboxContainerRef}
        >
          {data.map((datum, index) => {
            const id = getIdKey(datum, index, data);
            return (
              <MemoizedDataListItem
                key={id}
                datum={datum}
                id={id}
                dataRenderer={dataRenderer}
                loading={loading}
                firstItem={index === 0}
              />
            );
          })}
        </ul>
      );
    };

    export const DataListWithLoader = <Datum extends BasicDatum>(
      props: WrapperProps<Datum> & Props<Datum> & HTMLAttributes<HTMLElement>
    ) => <>{withDataLoader<Datum, typeof props>(DataList)(props)}</>;