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
- I receive a list of JSON data from the backend, where dates are stored as strings in the format
'MM/DD/YYYY'
. - I initially sort this data before assigning it to Formik's
initialValues
. - The Formik form and table are then rendered.
- Clicking on the table header sorts the data.
- Editable fields allow modification of data.
- View-only date fields are converted back to string format
'MM/DD/YYYY'
(displayValue
). - Editable date fields are sent as-is (
cellValue
). - 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
- I receive a list of JSON data from the backend, where dates are stored as strings in the format
'MM/DD/YYYY'
. - I initially sort this data before assigning it to Formik's
initialValues
. - The Formik form and table are then rendered.
- Clicking on the table header sorts the data.
- Editable fields allow modification of data.
- View-only date fields are converted back to string format
'MM/DD/YYYY'
(displayValue
). - Editable date fields are sent as-is (
cellValue
). - 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 badges1 Answer
Reset to default 0I 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
版权声明:本文标题:reactjs - Trouble with Date Sorting and Debounced Text Input using Formik and MUI Table - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744706341a2620866.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论