admin管理员组文章数量:1123036
enter image description here I am self-taught so any advices / improvements will be highly appreciated. I use Data Grid from MUI and react-focus-on library. I also use zustand store. I have csv table component where I have data table and when data table is in focus and the data inside is being changed those data are updated in const rows. When user clicks outside of csv table component I need those data to be stored/updated in the zustand store. When user is editing data in the table I don't want to update data in the store all the time because it would cause rerender all the time (if you see any improvement here any advices would be appreciated).
The problem is when used is finished with editing a cell in the data table and clicks outside from csv table component onClickOutside is triggered where I handle what is supposed to happen but it's triggered earlier then processRowUpdate or onCellEditStop in data grid what is a problem because when user is done with editing a cell I need to update the data and that wont happen when processRowUpdate or onCellEditStop is not triggered. I tried to solve it with flags but in the code what I have now I have problem that when I update rows in handleRowEdit and then persistChanges is called there I have old rows data. But it's a bad workaround what I don't want to use if there is better solution for onFocus.
Any ideas how can tell on focus to wait for datagrid to be finished and then trigger onClickOutside? Thank you.
const CsvTable = () => {
const {
models,
selectedFileKey,
editModeAllowed,
loadingEditorArea,
selectedCustomer,
isDarkMode,
selectedCsvTableRow
} = usePersistStore(useShallow((state) => ({
models: state.models,
selectedFileKey: state.selectedFileKey,
editModeAllowed: state.menuAccess,
loadingEditorArea: state.loadingEditorArea,
selectedCustomer: state.selectedCustomer,
isDarkMode: state.isDarkMode,
selectedCsvTableRow: state.selectedCsvTableRow
})))
const theme = useTheme()
const [rows, setRows] = useState(getDataGridRows(models[selectedCustomer][selectedFileKey].csvTableData))
const [modifiedRows, setModifiedRows] = useState<Row[]>([])
const ref = useRef<any>(null)
const readOnlyTable = selectedFileKey.includes('libs') || selectedFileKey.includes('tmp') || !editModeAllowed
const dataColumns = useMemo(() => [
...models[selectedCustomer][selectedFileKey].csvTableColumns.map((column: string) => ({
field: column,
headerName: column,
editable: !readOnlyTable,
})),
], [selectedCustomer, selectedFileKey, editModeAllowed]
)
interface Row {
[key: string]: any
}
const [saveChanges, setSaveChanges] = useState(false)
let cellEditStopped = false
let handleRowEditFinished = false
const handleRowEdit = (updatedRow: Partial<Row>) => {
markFileAsEditedIfNecessary()
const updatedRows = updateRows(updatedRow)
setRows(updatedRows)
modifiedRows.push(updatedRow)
if (!handleRowEditFinished) {
handleRowEditFinished = true
onCellEditStop()
}
return updatedRow
}
const onCellEditStop = (row?: any) => {
console.log(row)
if (!handleRowEditFinished) {
return
}
if (!cellEditStopped) {
cellEditStopped = true
handleOutsideClick()
}
}
const handleOutsideClick = () => {
if (!cellEditStopped) {
return
}
persistChanges(rows) // Save changes when clicked outside
}
const markFileAsEditedIfNecessary = () => {
const fileData = models[selectedCustomer][selectedFileKey]
if (!fileData.fileEdited) {
fileSystemService.markFileAsEdited(selectedCustomer, selectedFileKey)
}
}
const updateRows = (updatedRow: Partial<Row>) => {
return rows.map(row =>
row.id === updatedRow.id ? {...row, ...updatedRow} : row
)
}
console.log('saveChanges: ', saveChanges)
const persistChanges = (updatedRows: Partial<Row[]>) => {
console.log(modifiedRows)
const store = usePersistStore.getState()
const modelsCopy = {...store.models[selectedCustomer]}
const updatedCsvContent = fileSystemService.updateCsv(
modelsCopy[selectedFileKey].content,
modifiedRows
)
modelsCopy[selectedFileKey] = {
...modelsCopy[selectedFileKey],
csvTableData: updatedRows,
content: updatedCsvContent,
}
store.setModels(selectedCustomer, modelsCopy)
setSaveChanges(false)
cellEditStopped = false
console.log('save')
}
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const [menuOpen, setMenuOpen] = React.useState(false)
const [menuPosition, setMenuPosition] = useState<any>(null)
const handleRowContextMenu = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault()
//console.log(event.currentTarget.dataset.id)
if (event.currentTarget.dataset.id) {
const id: GridRowSelectionModel = [event.currentTarget.dataset.id]
usePersistStore.getState().setSelectedCsvTableRow(id)
}
setMenuPosition({
mouseX: event.clientX,
mouseY: event.clientY,
})
setAnchorEl(event.currentTarget)
setMenuOpen(true)
}
const addRow = () => {
setMenuOpen(false)
const rdeStore = usePersistStore.getState()
const models = rdeStore.models
const csvTableData = models[selectedCustomer][selectedFileKey].csvTableData
const newRowId = Number(selectedCsvTableRow[0])
const newCsvTableData = insertEmptyRow(csvTableData, newRowId)
setRows(getDataGridRows(newCsvTableData))
usePersistStore.getState().setCsvTableData(selectedCustomer, selectedFileKey, newCsvTableData)
}
function isRowEmpty<T extends Record<string, any>>(row: T): boolean {
return Object.entries(row)
.filter(([key]) => key !== "id")
.every(([, value]) => value === "")
}
function deleteRowByIndex<T extends Record<string, any>>(data: T[], rowIdToDelete: number, fileContent: string): {
updatedData: T[];
updatedFileContent: string
} {
const rowIndex = data.findIndex(row => row.id === rowIdToDelete);
if (rowIndex === -1) {
throw new Error(`Row with ID ${rowIdToDelete} not found.`)
}
const rowToDelete = data[rowIndex];
if (isRowEmpty(rowToDelete)) {
const updatedData = data.filter(row => row.id !== rowIdToDelete)
const reindexedData = updatedData.map((row, index) => ({...row, id: index}))
return {updatedData: reindexedData, updatedFileContent: fileContent}
}
markFileAsEditedIfNecessary()
const lines = fileContent.split("\n")
lines.splice(rowIndex + 1, 1)
const updatedFileContent = lines.join("\n")
const updatedData = data.filter(row => row.id !== rowIdToDelete)
const reindexedData = updatedData.map((row, index) => ({...row, id: index}))
return {updatedData: reindexedData, updatedFileContent}
}
const removeRow = () => {
setMenuOpen(false)
const rdeStore = usePersistStore.getState()
const models = rdeStore.models
const csvTableData = models[selectedCustomer][selectedFileKey].csvTableData
const fileContent = models[selectedCustomer][selectedFileKey].content
const rowIdToDelete = Number(selectedCsvTableRow[0])
const {updatedData, updatedFileContent} = deleteRowByIndex(csvTableData, rowIdToDelete, fileContent)
setRows(getDataGridRows(updatedData))
usePersistStore.getState().setCsvTableData(selectedCustomer, selectedFileKey, updatedData)
usePersistStore.getState().setContent(selectedCustomer, selectedFileKey, updatedFileContent)
}
function insertEmptyRow<T extends { id: number }>(data: T[], position: number): T[] {
const emptyRow: T = Object.keys(data[0]).reduce((acc, key) => {
acc[key as keyof T] = key === 'id' ? position + 1 : '' as any
return acc
}, {} as T)
const newData: T[] = []
for (let i = 0; i < data.length; i++) {
const item = data[i]
newData.push(item)
if (i === position) {
newData.push(emptyRow)
}
}
return newData.map((item, index) => ({
...item,
id: index
}))
}
const handleClose = () => {
setMenuPosition(null)
setMenuOpen(false)
}
function CustomUserItem(props: GridColumnMenuItemProps) {
const {myCustomHandler, myCustomValue} = props;
return (
<MenuItem onClick={myCustomHandler}>
<ListItemIcon>
<SettingsApplicationsIcon fontSize="small"/>
</ListItemIcon>
<ListItemText>{myCustomValue}</ListItemText>
</MenuItem>
);
}
function CustomColumnMenu(props: GridColumnMenuProps) {
return (
<GridColumnMenu
{...props}
slots={{
// Add new item
columnMenuUserItem: CustomUserItem,
}}
slotProps={{
columnMenuUserItem: {
// set `displayOrder` for new item
displayOrder: 15,
// pass additional props
myCustomValue: 'Do custom action',
myCustomHandler: () => alert('Custom handler fired'),
},
}}
/>
);
}
return <>
{loadingEditorArea ? (
<div className="tree-directory">
<ProgressSpinner className="progress-spinner flex justify-content-center" strokeWidth="4"
animationDuration=".5s"/>
</div>) : (
<FocusOn
enabled={saveChanges}
onClickOutside={handleOutsideClick}
onEscapeKey={() => setSaveChanges(false)}
>
<Box
sx={{
display: 'flex',
flex: 1,
position: 'absolute',
height: '-webkit-fill-available',
width: '-webkit-fill-available',
marginRight: '5px'
}}
ref={ref}
>
<DataGrid
onColumnHeaderContextMenu={() => console.log('something')}
onColumnHeaderClick={() => console.log('header click')}
autosizeOptions={{
includeOutliers: true,
includeHeaders: true,
}}
rowSelectionModel={selectedCsvTableRow}
rows={rows}
columns={dataColumns}
onRowSelectionModelChange={(selectedRow) => usePersistStore.getState().setSelectedCsvTableRow(selectedRow)}
getRowId={(row) => row.id}
processRowUpdate={(updatedRow) => handleRowEdit(updatedRow)}
onCellEditStart={() => {
setSaveChanges(true)
}}
onCellEditStop={(rowNode) => {
console.log(rowNode)
onCellEditStop(rowNode.row)
}}
initialState={{
pagination: {
paginationModel: {
pageSize: 25,
},
},
}}
slotProps={{
toolbar: {
showQuickFilter: true,
},
row: {
onContextMenu: handleRowContextMenu,
style: {cursor: 'context-menu'},
},
}}
slots={readOnlyTable ? undefined : { columnMenu: CustomColumnMenu }}
density="compact"
pageSizeOptions={[10, 25, 35, 50]}
showCellVerticalBorder
showColumnVerticalBorder
//disableDensitySelector
disableColumnSelector={false}
autosizeOnMount
sx={{
border: theme.palette.mode === 'dark' ?
'1px solid rgba(255, 255, 255, 0.12)'
: '1px solid rgba(0, 0, 0, 0.12)',
'& .MuiDataGrid-columnHeaders': {
backgroundColor: theme.palette.mode === 'light'
? '#f0f0f0'
: '#262626',
},
'& .MuiDataGrid-virtualScroller': {
backgroundColor: theme.palette.mode === 'light'
? '#f9f9f9'
: '#393a39',
},
'& .MuiDataGrid-withBorderColor': {
backgroundColor: theme.palette.mode === 'light'
? '#f0f0f0'
: '#262626',
},
}}
/>
{!readOnlyTable ?
<Menu
open={menuOpen}
onClose={handleClose}
anchorEl={anchorEl}
anchorReference="anchorPosition"
anchorPosition={
menuPosition ? {top: menuPosition.mouseY, left: menuPosition.mouseX} : undefined
}
slotProps={{
paper: {
elevation: 0,
sx: {
backgroundColor: isDarkMode ? '#f5f5f5' : '#393a39',
color: isDarkMode ? 'black' : 'white',
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'&::before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 14,
width: 10,
height: 10,
bgcolor: isDarkMode ? '#f5f5f5' : '#393a39',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
},
}}
>
<MenuItem onClick={addRow}>Add row</MenuItem>
<MenuItem onClick={removeRow}>Delete row</MenuItem>
</Menu>
: ''
}
</Box>
</FocusOn>
)}
</>
}
本文标签: reactjsMaterial UI data grid with react focusStack Overflow
版权声明:本文标题:reactjs - Material UI data grid with react focus - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736543638a1944417.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论