1

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:

  • checked: when all the child rows are checked
  • empty: when no children row is selected
  • intermediate: when some of the child rows are selected

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?

2
  • react-data-table-component has a select feature (react-data-table-component.netlify.app/?path=/docs/…) Why not use it? Commented Jun 7, 2022 at 10:16
  • I have seen that but it doesn't support my requirement. I need to implement select in the expanded rows as well. Commented Jun 7, 2022 at 12:15

1 Answer 1

2

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)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.