2

long story short I was asked to create a table component using react-table,

in that table by default it uses component input, which when double-clicked it can immediately type.

and secondly, I want for one of the column editableCell to use dropdown. i have tried it but failed.

depedencies:

{
  "@tanstack/react-table": "^8.5.22",
  "next": "^12.3.2",
  "react": "^17.0.2",
  "react-dom": "^17.0.2"
}

component used : SubTable

<SubTable
   data={dataTable}
   handleAdd={addData}
   columns={DataColumns}
   tableStyle="h-[250px]"
   hiddenColumns={["id"]}
   showAddButton={true}
   showDeleteButton={true}
   skipPageReset={skipPageReset}
   updateData={updateMyData}
/>

SubTable.tsx

import { FC, useState } from "react";
import {
  useFlexLayout,
  usePagination,
  useTable,
  useGlobalFilter,
  useSortBy,
} from "react-table";
import styles from "./styles.module.css";
import EditableCell from "./EditableCell";
import Image from "next/image";

export interface SubTableProps {
  setIsForm?: stateData;
  data: any[];
  columns: any[];
  headerComponent?: JSX.Element;
  page?: (page: number) => void;
  showAddButton?: boolean;
  showDeleteButton?: boolean;
  onClickRow?: (cell: Cell<any, unknown>, row: Row<any>) => void;
  hiddenColumns?: string[];
  updateData?: (row: any, columnId: string, value: any) => void;
  skipPageReset?: boolean;
  tableStyle?: string;
  handleAdd?: () => void;
  handleConfig?: () => void;
  deleteLabel?: string;
  showBedButton?: boolean;
  showConfigButton?: boolean;
}

const SubTable: FC<SubTableProps> = ({
  data,
  columns,
  hiddenColumns,
  skipPageReset,
  updateData,
  setIsForm,
  tableStyle,
  showAddButton,
  showDeleteButton,
  showBedButton = false,
  showConfigButton = false,
  deleteLabel = "Delete",
  handleAdd,
  handleConfig,
}) => {
  const [editableRowIndex, setEditableRowIndex] = useState(null);
  const [active, setActive] = useState("");
  const [selectedData, setSelectedData] = useState({});

  const { getTableProps, getTableBodyProps, headerGroups, page, prepareRow } =
    useTable(
      {
        columns,
        data,
        // Set our editable cell renderer as the default Cell renderer
        defaultColumn: {
          Cell: EditableCell,
        },
        autoResetPage: !skipPageReset ?? false,
        initialState: {
          pageIndex: 0,
          hiddenColumns: hiddenColumns ?? [],
        },
        updateData,
        editableRowIndex,
        setEditableRowIndex,
      },
      useGlobalFilter,
      useSortBy,
      usePagination,
      useFlexLayout ?? null
    );

  const clickedRow = (e: React.MouseEvent, result: any) => {
    const { index, values } = result;
    setActive(index);

    const currentIndex = index;
    if (e.detail == 2) {
      if (editableRowIndex !== currentIndex) {
        setEditableRowIndex(index);
      } else {
        setEditableRowIndex(null);
        setSelectedData(values);
        // data changed here console.log('update row', values)
      }
    }
  };
  return (
    <>
      <div className={`bg-white p-2 text-xs ${tableStyle ?? ""}`}>
        {/*  */}
        <div className={styles.toolbar}>
          {showAddButton && (
            <>
              <button className={styles.btn} type="button" onClick={handleAdd}>
                <Image
                  src="/images/button/add.png"
                  className="ml-1"
                  alt="add_icon"
                  width={16}
                  height={16}
                />
                &nbsp;Add
              </button>
              <div className={styles.separate}>|</div>
            </>
          )}

          {showConfigButton && (
            <>
              <button
                className={styles.btn}
                type="button"
                onClick={handleConfig}
              >
                <Image
                  src="/images/button/update.svg"
                  className="ml-1"
                  alt="add_icon"
                  width={16}
                  height={16}
                />
                &nbsp;Configuration
              </button>
              <div className={styles.separate}>|</div>
            </>
          )}

          {showDeleteButton && (
            <button
              className={styles.btn}
              type="button"
              onClick={() => {
                console.log("delete");
              }}
            >
              <Image
                src="/images/button/delete.svg"
                className="ml-1"
                alt="delete_icon"
                width={16}
                height={16}
              />
              &nbsp;{deleteLabel}
            </button>
          )}

          {showBedButton && (
            <button
              className={styles.btn}
              type="button"
              onClick={() => {
                console.log("delete");
              }}
            >
              <Image
                src="/images/button/edit-undo.png"
                className="ml-1"
                alt="delete_icon"
                width={16}
                height={16}
              />
              &nbsp;Empty Bed
            </button>
          )}
        </div>
        <div className="overflow-x-auto border-l-[#e7e9ec] border-r-[#e7e9ec] print:block max-h-[200px] overflow-y-auto">
          <table
            className="table-fixed w-full border-x bg-white relative border-collapse"
            {...getTableProps()}
          >
            <thead className="sticky top-0">
              {headerGroups.map((headerGroup, idx) => (
                <tr {...headerGroup.getHeaderGroupProps()} key={idx}>
                  {headerGroup.headers.map((column, idx) => (
                    <th
                      className="border border-solid font-normal text-lg text-left p-1 bg-green-100"
                      {...column.getHeaderProps(column.getSortByToggleProps())}
                      key={idx}
                    >
                      {column.render("Header")}
                      <span>
                        {column.isSorted
                          ? column.isSortedDesc
                            ? " "
                            : " "
                          : " ↕"}
                      </span>
                    </th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody className="overflow-y-auto" {...getTableBodyProps()}>
              {page.map((row, idx) => {
                prepareRow(row);
                return (
                  <tr
                    {...row.getRowProps()}
                    key={idx}
                    className={`${
                      active == row.id ? "bg-bgGrey-1" : ""
                    } cursor-default`}
                    onClick={(e) => clickedRow(e, row)}
                  >
                    {row.cells.map((cell, idx) => {
                      return (
                        <td
                          className="whitespace-nowrap text-ellipsis overflow-hidden border-b border-r border-y p-1"
                          {...cell.getCellProps()}
                          key={idx}
                        >
                          {cell.render("Cell")}
                        </td>
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
};

export default SubTable;

useSubTable.tsx

import { useState } from "react";

const useSubTable = () => {
  const [dataTable, setDataTable] = useState<any>([]);
  const [skipPageReset, setSkipPageReset] = useState(false);

  const updateMyData = (rowIndex: number, columnId: any, value: any) => {
    // We also turn on the flag to not reset the page
    setSkipPageReset(true);
    setDataTable((old: any[]) =>
      old.map((row: any, index: number) => {
        if (index === rowIndex) {
          return {
            ...old[rowIndex],
            [columnId]: value,
          };
        }
        return row;
      })
    );
  };

  return { dataTable, setDataTable, updateMyData, skipPageReset };
};

export default useSubTable;

EditableCell.tsx

import React, { useState, useEffect } from "react";

// Create an editable cell renderer
const EditableCell = (props: any) => {
  const {
    value: initialValue,
    row: { index },
    column: { id },
    updateData, // This is a custom function that we supplied to our table instance
    editableRowIndex, // index of the row we requested for editing
  } = props;
  // We need to keep and update the state of the cell normally
  const [value, setValue] = useState(initialValue);

  const onChange = (e: any) => {
    setValue(e.target.value);
  };

  // We'll only update the external data when the input is blurred
  const onBlur = () => {
    updateData(index, id, value);
  };

  // If the initialValue is changed externall, sync it up with our state
  useEffect(() => {
    setValue(initialValue);
  }, [initialValue]);

  return index === editableRowIndex ? (
    <input
      style={{ width: "100%" }}
      value={value}
      onChange={onChange}
      onBlur={onBlur}
    />
  ) : (
    <p>{value}</p>
  );
};

export default EditableCell;

define a column: column.ts

import TypeDropdown from "./TypeDropdown";

export const dataColumns = [
  {
    Header: "Name",
    accessor: "name",
    sticky: "left",
  },
  {
    Header: "Order",
    accessor: "order",
    sticky: "left",
  },
  {
    Header: "Type",
    accessor: "type",
    sticky: "left",
    Cell: TypeDropdown,
  },
];

my custom dropdown component: TypeDropdown.tsx

import React, { useState, useEffect } from "react";
import { Select } from "@/client/components/Inputs";

const TypeDropdown = (props: any) => {
  const {
    value: initialValue,
    row: { index },
    column: { id },
    updateData, // This is a custom function that we supplied to our table instance
    editableRowIndex, // index of the row we requested for editing
  } = props;
  // We need to keep and update the state of the cell normally
  const [value, setValue] = useState(initialValue);

  const onChange = (e: any) => {
    setValue(e.target.value);
    updateData(index, id, e.target.value);
  };

  // If the initialValue is changed externall, sync it up with our state
  useEffect(() => {
    setValue(initialValue);
  }, [initialValue]);

  return id === "type" ? (
    <Select
      value={value}
      id="typex"
      option={[
        { id: 1, name: "TextField", code: "TextField" },
        { id: 2, name: "Combo", code: "Combo" },
      ]}
      showRed={true}
      onChange={(e) => onChange(e)}
      required={true}
    />
  ) : (
    <p>{value}</p>
  );
};

export default TypeDropdown;

lastly my useSubTableProps.tsx

import React, { useMemo, useState } from "react";
import { dataColumns } from "./column";

interface customObject {
  [key: string]: any;
}

const useSubTableProps = () => {
  const DataColumns = useMemo(() => dataColumns, []);

  const [dataTable, setDataTable] = useState<any>([]);
  const [skipPageReset, setSkipPageReset] = useState<boolean>(false);

  const updateMyData = (rowIndex: number, columnId: any, value: any) => {
    // We also turn on the flag to not reset the page
    setSkipPageReset(true);
    setDataTable((old: customObject[]) =>
      old.map((row: any, index: number) => {
        if (index === rowIndex) {
          return {
            ...old[rowIndex],
            [columnId]: value,
          };
        }
        return row;
      })
    );
  };

  const addData = () => {
    setDataTable((prev: any) => [
      ...prev,
      {
        name: `test`,
        order: 0,
        type: "TextField",
      },
    ]);
  };

  return { DataColumns, dataTable, addData, updateMyData, skipPageReset };
};

export default useSubTableProps;

expected visual, what I want in column type is dropdown component enter image description here

Heru Wijayanto
  • 439
  • 3
  • 5
  • 16

1 Answers1

0

finally, I found the problem and the solution,

after I re-read the documentation about the column options section, it says to use a valid JSX element, after that, I try to use a simple JSX element and it works. Maybe my previous JSX element was not considered valid by react-table

link: React-table column options

enter image description here

Heru Wijayanto
  • 439
  • 3
  • 5
  • 16