admin管理员组

文章数量:1389841

I am having trouble with sorting and handling editable date fields using Formik in an MUI table. Below is a detailed explanation of my flow along with contextual code. Additionally, I'm facing issues with the invoice_notes field behaving inconsistently due to debounce. Below is the flow with contextual code.

Flow Overview

  1. I receive a list of JSON data from the backend, where dates are stored as strings in the format 'MM/DD/YYYY'.
  2. I initially sort this data before assigning it to Formik's initialValues.
  3. The Formik form and table are then rendered.
  4. Clicking on the table header sorts the data.
  5. Editable fields allow modification of data.
  6. View-only date fields are converted back to string format 'MM/DD/YYYY' (displayValue).
  7. Editable date fields are sent as-is (cellValue).
  8. The invoice_notes field updates weirdly when typing—after an input, part of the text disappears, and the cursor is removed. After re-entering text, a debounce delay causes previous input changes to be lost again.

State Management

const [order, setOrder] = useState("asc");
const [orderBy, setOrderBy] = useState(headers[0]);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(20);

I receive invoiceTableData from a parent component into my table component.

Date Parsing Function

const parseDates = (data) => {
  return data.map((item) => {
    const parsedItem = { ...item };
    Object.keys(parsedItem).forEach((key) => {
      if (typeof parsedItem[key] === "string" && /\d{1,2}\/\d{1,2}\/\d{4}/.test(parsedItem[key])) {
        parsedItem[key] = new Date(parsedItem[key]);
      }
    });
    return parsedItem;
  });
};

Sorting Data Before Passing to Formik

const initialSortedData = useMemo(() => {
  const parsedData = parseDates(invoiceTableData);
  return [...parsedData].sort((a, b) => {
    const valueA = a[orderBy];
    const valueB = b[orderBy];
    if (valueA instanceof Date && valueB instanceof Date) {
      return order === "asc" ? valueA - valueB : valueB - valueA;
    }
    return valueA < valueB ? (order === "asc" ? -1 : 1) : valueA > valueB ? (order === "asc" ? 1 : -1) : 0;
  });
}, []);

Sorting Handler Function

const handleSort = (header, orderBy, setOrderBy, order, setOrder, values, setFieldValue) => {
  const isAsc = orderBy === header && order === "asc";
  const newOrder = isAsc ? "desc" : "asc";

  const sortedData = [...values.data]
    .map((item) => ({ ...item }))
    .sort((a, b) => {
      let valueA = a[header];
      let valueB = b[header];

      const isNullA = valueA === null || valueA === "Invalid date" || valueA === "0000-00-00";
      const isNullB = valueB === null || valueB === "Invalid date" || valueB === "0000-00-00";
      if (isNullA && isNullB) return 0;
      if (isNullA) return 1;
      if (isNullB) return -1;

      if (!(valueA instanceof Date) && typeof valueA === "string" && /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(valueA)) {
        valueA = convertToDate(valueA);
      }
      if (!(valueB instanceof Date) && typeof valueB === "string" && /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(valueB)) {
        valueB = convertToDate(valueB);
      }

      if (valueA instanceof Date && valueB instanceof Date) {
        return newOrder === "asc" ? valueA - valueB : valueB - valueA;
      }
      return valueA < valueB ? (newOrder === "asc" ? -1 : 1) : valueA > valueB ? (newOrder === "asc" ? 1 : -1) : 0;
    });

  setFieldValue("data", [...sortedData]);
  setOrderBy(header);
  setOrder(newOrder);
};

Formik Initialization and Table Rendering

<Formik initialValues={{ data: initialSortedData }} innerRef={formRef}>
  <Form>
    <TableContainer>
      <Table>
        <TableHead>
          <TableRow>
            {headers.map((header) => (
              <TableCell key={header}>
                <TableSortLabel
                  active={orderBy === header}
                  direction={orderBy === header ? order : "asc"}
                  onClick={() => handleSort(header, orderBy, setOrderBy, order, setOrder, values, setFieldValue)}
                >
                  {getHeaderLabel(header)}
                </TableSortLabel>
              </TableCell>
            ))}
          </TableRow>
        </TableHead>
<TableBody>
      {values.data
        .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
        .map((row, index) => {
          const actualIndex = page * rowsPerPage + index; // Corrected index
          return (
            <TableRow
              className={globalClasses.TableRow}
              key={actualIndex}
              style={{
                fontSize: "1em",
                color: "#4e4e4e",
              }}
            >
              {headers.map((header) => {
                const cellValue = row[header];

                // Check if the value is a valid date and convert it to a Date object
                const isDate =
                  cellValue instanceof Date ||
                  (typeof cellValue === "string" &&
                    !isNaN(Date.parse(cellValue)) &&
                    cellValue.includes("-"));

                const parsedDate = isDate ? new Date(cellValue) : null;

                // Format date to MM/DD/YYYY if it's a valid date
                const displayValue = parsedDate
                  ? parsedDate.toLocaleDateString("en-US")
                  : cellValue;

                const fieldTypes = {
                  amount: "number",
                  invoice_notes: "text",
                  invoice_created_at: "date",
                  payment_date: "date",
                };

                return (
                  <TableCell
                    className={globalClasses.TableCellField}
                    key={header}
                    style={{ padding: 10, textAlign: "center" }}
                  >
                    {fieldTypes[header] ? (
                      <EditableTableFieldInvoice
                        type={fieldTypes[header]}
                        name={`data.${actualIndex}.${header}`}
                        setFieldValue={setFieldValue}
                        parentFormRef={formRef}
                        value={cellValue}
                      />
                    ) : (
                      <>
                        {displayValue}
                        {/* {isDate ? row[header].toLocaleDateString("en-US") : row[header]} */}
                      </>
                    )}
                  </TableCell>
                );
              })}
            </TableRow>
          );
        })}
    </TableBody>
      </Table>
    </TableContainer>
    <TablePagination rowsPerPageOptions={[10, 20, 30]} count={values.data.length} rowsPerPage={rowsPerPage} page={page} />
  </Form>
</Formik>

Editable Field Component

const EditableTableFieldInvoice = React.memo(({ name, type, value, setFieldValue, parentFormRef }) => {
  const [localValue, setLocalValue] = useState(value);

  const updateFormik = debounce((val) => {
    parentFormRef.current.setFieldValue(name, val);
  }, 500);

  const formatToYYYYMMDD = (date) => {
    if (!date) return "";
    if (!(date instanceof Date)) date = new Date(date);
    return isNaN(date.getTime()) ? "" : date.toISOString().split("T")[0];
  };

  return (
    <Field
      name={name}
      type={type}
      component={FMUInputField}
      value={type === "date" ? formatToYYYYMMDD(localValue) : localValue}
      onChange={(e) => {
                switch (type) {
                  case "date":
                    const inputValue = e.target.value;
                    const formattedDate = new Date(inputValue);
                    setLocalValue(formattedDate);
                    updateFormik(formattedDate);
                    break;

                  case "number":
                    const numberValue = Number(e.target.value);
                    setLocalValue(numberValue);
                    updateFormik(numberValue);
                    break;

                  default:
                    const updateValue = e.target.value;
                    setLocalValue(updateValue);
                    updateFormik(updateValue);
                    break;
                }
              }}
    />
  );
});

Issue

The initial sort (ascending to descending) works, but sorting back to ascending sometimes fails. I suspect a state management issue, but my fixes to handleSort have not resolved it.

How can I fix this issue?

I am having trouble with sorting and handling editable date fields using Formik in an MUI table. Below is a detailed explanation of my flow along with contextual code. Additionally, I'm facing issues with the invoice_notes field behaving inconsistently due to debounce. Below is the flow with contextual code.

Flow Overview

  1. I receive a list of JSON data from the backend, where dates are stored as strings in the format 'MM/DD/YYYY'.
  2. I initially sort this data before assigning it to Formik's initialValues.
  3. The Formik form and table are then rendered.
  4. Clicking on the table header sorts the data.
  5. Editable fields allow modification of data.
  6. View-only date fields are converted back to string format 'MM/DD/YYYY' (displayValue).
  7. Editable date fields are sent as-is (cellValue).
  8. The invoice_notes field updates weirdly when typing—after an input, part of the text disappears, and the cursor is removed. After re-entering text, a debounce delay causes previous input changes to be lost again.

State Management

const [order, setOrder] = useState("asc");
const [orderBy, setOrderBy] = useState(headers[0]);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(20);

I receive invoiceTableData from a parent component into my table component.

Date Parsing Function

const parseDates = (data) => {
  return data.map((item) => {
    const parsedItem = { ...item };
    Object.keys(parsedItem).forEach((key) => {
      if (typeof parsedItem[key] === "string" && /\d{1,2}\/\d{1,2}\/\d{4}/.test(parsedItem[key])) {
        parsedItem[key] = new Date(parsedItem[key]);
      }
    });
    return parsedItem;
  });
};

Sorting Data Before Passing to Formik

const initialSortedData = useMemo(() => {
  const parsedData = parseDates(invoiceTableData);
  return [...parsedData].sort((a, b) => {
    const valueA = a[orderBy];
    const valueB = b[orderBy];
    if (valueA instanceof Date && valueB instanceof Date) {
      return order === "asc" ? valueA - valueB : valueB - valueA;
    }
    return valueA < valueB ? (order === "asc" ? -1 : 1) : valueA > valueB ? (order === "asc" ? 1 : -1) : 0;
  });
}, []);

Sorting Handler Function

const handleSort = (header, orderBy, setOrderBy, order, setOrder, values, setFieldValue) => {
  const isAsc = orderBy === header && order === "asc";
  const newOrder = isAsc ? "desc" : "asc";

  const sortedData = [...values.data]
    .map((item) => ({ ...item }))
    .sort((a, b) => {
      let valueA = a[header];
      let valueB = b[header];

      const isNullA = valueA === null || valueA === "Invalid date" || valueA === "0000-00-00";
      const isNullB = valueB === null || valueB === "Invalid date" || valueB === "0000-00-00";
      if (isNullA && isNullB) return 0;
      if (isNullA) return 1;
      if (isNullB) return -1;

      if (!(valueA instanceof Date) && typeof valueA === "string" && /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(valueA)) {
        valueA = convertToDate(valueA);
      }
      if (!(valueB instanceof Date) && typeof valueB === "string" && /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(valueB)) {
        valueB = convertToDate(valueB);
      }

      if (valueA instanceof Date && valueB instanceof Date) {
        return newOrder === "asc" ? valueA - valueB : valueB - valueA;
      }
      return valueA < valueB ? (newOrder === "asc" ? -1 : 1) : valueA > valueB ? (newOrder === "asc" ? 1 : -1) : 0;
    });

  setFieldValue("data", [...sortedData]);
  setOrderBy(header);
  setOrder(newOrder);
};

Formik Initialization and Table Rendering

<Formik initialValues={{ data: initialSortedData }} innerRef={formRef}>
  <Form>
    <TableContainer>
      <Table>
        <TableHead>
          <TableRow>
            {headers.map((header) => (
              <TableCell key={header}>
                <TableSortLabel
                  active={orderBy === header}
                  direction={orderBy === header ? order : "asc"}
                  onClick={() => handleSort(header, orderBy, setOrderBy, order, setOrder, values, setFieldValue)}
                >
                  {getHeaderLabel(header)}
                </TableSortLabel>
              </TableCell>
            ))}
          </TableRow>
        </TableHead>
<TableBody>
      {values.data
        .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
        .map((row, index) => {
          const actualIndex = page * rowsPerPage + index; // Corrected index
          return (
            <TableRow
              className={globalClasses.TableRow}
              key={actualIndex}
              style={{
                fontSize: "1em",
                color: "#4e4e4e",
              }}
            >
              {headers.map((header) => {
                const cellValue = row[header];

                // Check if the value is a valid date and convert it to a Date object
                const isDate =
                  cellValue instanceof Date ||
                  (typeof cellValue === "string" &&
                    !isNaN(Date.parse(cellValue)) &&
                    cellValue.includes("-"));

                const parsedDate = isDate ? new Date(cellValue) : null;

                // Format date to MM/DD/YYYY if it's a valid date
                const displayValue = parsedDate
                  ? parsedDate.toLocaleDateString("en-US")
                  : cellValue;

                const fieldTypes = {
                  amount: "number",
                  invoice_notes: "text",
                  invoice_created_at: "date",
                  payment_date: "date",
                };

                return (
                  <TableCell
                    className={globalClasses.TableCellField}
                    key={header}
                    style={{ padding: 10, textAlign: "center" }}
                  >
                    {fieldTypes[header] ? (
                      <EditableTableFieldInvoice
                        type={fieldTypes[header]}
                        name={`data.${actualIndex}.${header}`}
                        setFieldValue={setFieldValue}
                        parentFormRef={formRef}
                        value={cellValue}
                      />
                    ) : (
                      <>
                        {displayValue}
                        {/* {isDate ? row[header].toLocaleDateString("en-US") : row[header]} */}
                      </>
                    )}
                  </TableCell>
                );
              })}
            </TableRow>
          );
        })}
    </TableBody>
      </Table>
    </TableContainer>
    <TablePagination rowsPerPageOptions={[10, 20, 30]} count={values.data.length} rowsPerPage={rowsPerPage} page={page} />
  </Form>
</Formik>

Editable Field Component

const EditableTableFieldInvoice = React.memo(({ name, type, value, setFieldValue, parentFormRef }) => {
  const [localValue, setLocalValue] = useState(value);

  const updateFormik = debounce((val) => {
    parentFormRef.current.setFieldValue(name, val);
  }, 500);

  const formatToYYYYMMDD = (date) => {
    if (!date) return "";
    if (!(date instanceof Date)) date = new Date(date);
    return isNaN(date.getTime()) ? "" : date.toISOString().split("T")[0];
  };

  return (
    <Field
      name={name}
      type={type}
      component={FMUInputField}
      value={type === "date" ? formatToYYYYMMDD(localValue) : localValue}
      onChange={(e) => {
                switch (type) {
                  case "date":
                    const inputValue = e.target.value;
                    const formattedDate = new Date(inputValue);
                    setLocalValue(formattedDate);
                    updateFormik(formattedDate);
                    break;

                  case "number":
                    const numberValue = Number(e.target.value);
                    setLocalValue(numberValue);
                    updateFormik(numberValue);
                    break;

                  default:
                    const updateValue = e.target.value;
                    setLocalValue(updateValue);
                    updateFormik(updateValue);
                    break;
                }
              }}
    />
  );
});

Issue

The initial sort (ascending to descending) works, but sorting back to ascending sometimes fails. I suspect a state management issue, but my fixes to handleSort have not resolved it.

How can I fix this issue?

Share Improve this question edited Mar 17 at 22:51 Olivier Tassinari 8,6916 gold badges25 silver badges28 bronze badges asked Mar 13 at 10:51 Usman Khalid MianUsman Khalid Mian 135 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

I think the issue is with null value checking logic in the handleSort () function. It is hardcoded that null values would always be sorted in the same direction, preventing proper reversal of the sort order.

Can you try with the below logic ?

if (isNullA && isNullB) return 0;

if (isNullA) return newOrder === "asc" ? 1 : -1;

if (isNullB) return newOrder === "asc" ? -1 : 1;

本文标签: reactjsTrouble with Date Sorting and Debounced Text Input using Formik and MUI TableStack Overflow