Ravgeet Dhillon
Ravgeet Dhillon

Reputation: 590

How to select multiple rows and sub rows in React?

I'm looking for some help to implement row select functionality in my React app.

I have the following data that I provide to the table(react-data-table-component):

const data = useMemo(
  () =>
    [...Array(87)].map((_, outer) => ({
      id: `o-${outer}`,
      name: randFullName(),
      age: randNumber({ max: 100 }),
      price: randNumber({ max: 5000 }),
      description: randProductDescription(),
      active: randBoolean(),
      date: randFutureDate().toLocaleString(),
      items: [...Array(randNumber({ max: 10 }))].map((__, inner) => ({
        id: `o-${outer}--i-${inner}`,
        name: randFullName(),
        age: randNumber({ max: 100 }),
        price: randNumber({ max: 5000 }),
        description: randProductDescription(),
        active: randBoolean(),
        date: randFutureDate().toLocaleString(),
      })),
    })),
  [],
);

I display this data in a table.

There is a parent row and each parent row can be expanded which contains the child rows.

enter image description here

Now, I want to be able to select rows. I use a three-state checkbox where three states are:

I want to render the checkbox states and also be able to access the selected rows.

I am using a custom selector cell like this:

const SelectorCell = ({
  rowId,
  parentId,
  siblingIds,
  childrenIds,
}: {
  rowId: string;
  parentId: string;
  siblingIds: string[];
  childrenIds: string[];
}) => {
  const { toggleRow, rowSelectionStatus } = useContext(
    NewTableSelectedRowsContext,
  ) as NewTableSelectedRowsType;

  const handleToggle = useCallback(() => {
    // todo
  }, []);

  const status = rowSelectionStatus(rowId);

  return <ThreeStateCheckbox checked={status} onChange={handleToggle} />;
};

I have tried to achieve this using a context provider but haven't got any success:

import { createContext, useCallback, useMemo, useState } from "react";
import {
  NewTableSelectedRowsType,
} from "./types";

const statusTypes = {
  checked: true,
  intermediate: null,
  unchecked: false,
};

export const NewTableSelectedRowsContext = createContext<NewTableSelectedRowsType | null>(null);

interface Props {
  children: JSX.Element;
}

export const NewTableSelectedRowsContextProvider = ({ children }: Props): JSX.Element => {
  const rowSelectionStatus = useCallback((rowId: string) => // todo, []);

  const toggleRow = useCallback(() => {
      // todo
    },
    [],
  );

  const value: NewTableSelectedRowsType = useMemo(
    () => ({
      toggleRow,
      rowSelectionStatus,
    }),
    [rowSelectionStatus, toggleRow],
  );

  return (
    <NewTableSelectedRowsContext.Provider value={value}>
      {children}
    </NewTableSelectedRowsContext.Provider>
  );
};

How can I achieve that in React?

Upvotes: 1

Views: 2803

Answers (1)

Hagai Harari
Hagai Harari

Reputation: 2877

I'm not sure why you are using context to store the logic of selecting rows or not, you will need to send the data.length to compute if the selected rows are the same amount as the table rows/part of absolutely 0. You also haven't attached to component code, so my answer will be more general...

You can have state for table status and one for selected rows

enum ETableState {
  checked = 'checked',
  empty = 'empty',
  intermediate = 'intermediate'
}

const [tableState, setTableState] = useState<ETableState>(ETableState.intermediate)
const [selectedRows, setSelectedRows] = useState<string[])([])

on each selecting row, you will call a callback for add/remove it from selectedRows state, that after will trigger an effect

const toggleRow = useCallback((id: string) =>
    setSelectedRows(prev => 
        prev.includes(id) ? prev.filter(i => i != id) : [...prev, id]
    )
 , [selectedRows.length])  

useEffect(() => {
  setTableState(dataLength === selectedRows.length ?
                  ETableState.checked 
                : !selectedRows.length ?
                  ETableState.empty
                : ETableState.intermediate
 }
 ,[selectedRows.length, dataLength]

edit

In case you want to be able to use this logic in few separate places, as in child rows and so on, you can wrap all of this code in custom hook and call it in each component with the data.length

function useTableState(dataLength: number) {
  ...
  return { selectedRows, tableState, toggleRow }
}

// usage in a table component
const { selectedRows, tableState, toggleRow } = useTableState(data.length)

Upvotes: 2

Related Questions