fix stale updates in Grid/Table config

This commit is contained in:
jeffvli
2026-01-17 22:01:24 -08:00
parent bda82a8198
commit cad3b4c905
2 changed files with 172 additions and 235 deletions
@@ -11,7 +11,7 @@ import {
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview'; import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
import clsx from 'clsx'; import clsx from 'clsx';
import Fuse, { FuseResultMatch } from 'fuse.js'; import Fuse, { FuseResultMatch } from 'fuse.js';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from './table-config.module.css'; import styles from './table-config.module.css';
@@ -287,8 +287,7 @@ export const GridConfig = ({
<Divider /> <Divider />
<GridRowConfig <GridRowConfig
data={gridRowsData} data={gridRowsData}
listKey={listKey} onChange={(rows) => setList(listKey, { grid: { rows } })}
onChange={(rows) => setList(listKey, { ...list, grid: { ...grid, rows } })}
value={grid.rows} value={grid.rows}
/> />
</> </>
@@ -297,17 +296,23 @@ export const GridConfig = ({
const GridRowConfig = ({ const GridRowConfig = ({
data, data,
listKey,
onChange, onChange,
value, value,
}: { }: {
data: { label: string; value: string }[]; data: { label: string; value: string }[];
listKey: ItemListKey;
onChange: (value: ItemGridListRowConfig[]) => void; onChange: (value: ItemGridListRowConfig[]) => void;
value: ItemGridListRowConfig[]; value: ItemGridListRowConfig[];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const valueRef = useRef(value);
const onChangeRef = useRef(onChange);
useLayoutEffect(() => {
valueRef.current = value;
onChangeRef.current = onChange;
});
const labelMap = useMemo(() => { const labelMap = useMemo(() => {
return data.reduce( return data.reduce(
(acc, item) => { (acc, item) => {
@@ -318,79 +323,55 @@ const GridRowConfig = ({
); );
}, [data]); }, [data]);
const handleChangeEnabled = useCallback( const handleChangeEnabled = useCallback((item: ItemGridListRowConfig, checked: boolean) => {
(item: ItemGridListRowConfig, checked: boolean) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.grid.rows; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], isEnabled: checked };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], isEnabled: checked }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleMoveUp = useCallback( const handleMoveUp = useCallback((item: ItemGridListRowConfig) => {
(item: ItemGridListRowConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.grid.rows; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; if (index === 0) return;
const index = value.findIndex((v) => v.id === item.id); const newValues = [...currentValue];
if (index === 0) return; [newValues[index], newValues[index - 1]] = [newValues[index - 1], newValues[index]];
const newValues = [...value]; onChangeRef.current(newValues);
[newValues[index], newValues[index - 1]] = [newValues[index - 1], newValues[index]]; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleMoveDown = useCallback( const handleMoveDown = useCallback((item: ItemGridListRowConfig) => {
(item: ItemGridListRowConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.grid.rows; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; if (index === currentValue.length - 1) return;
const index = value.findIndex((v) => v.id === item.id); const newValues = [...currentValue];
if (index === value.length - 1) return; [newValues[index], newValues[index + 1]] = [newValues[index + 1], newValues[index]];
const newValues = [...value]; onChangeRef.current(newValues);
[newValues[index], newValues[index + 1]] = [newValues[index + 1], newValues[index]]; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleAlignLeft = useCallback( const handleAlignLeft = useCallback((item: ItemGridListRowConfig) => {
(item: ItemGridListRowConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.grid.rows; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], align: 'start' };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], align: 'start' }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleAlignCenter = useCallback( const handleAlignCenter = useCallback((item: ItemGridListRowConfig) => {
(item: ItemGridListRowConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.grid.rows; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], align: 'center' };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], align: 'center' }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleAlignRight = useCallback( const handleAlignRight = useCallback((item: ItemGridListRowConfig) => {
(item: ItemGridListRowConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.grid.rows; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], align: 'end' };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], align: 'end' }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const [searchRows, setSearchRows] = useDebouncedState('', 300); const [searchRows, setSearchRows] = useDebouncedState('', 300);
@@ -420,25 +401,20 @@ const GridRowConfig = ({
})); }));
}, [value, searchRows, fuse]); }, [value, searchRows, fuse]);
const handleReorder = useCallback( const handleReorder = useCallback((idFrom: string, idTo: string, edge: Edge | null) => {
(idFrom: string, idTo: string, edge: Edge | null) => { const currentValue = valueRef.current;
const currentValue = useSettingsStore.getState().lists[listKey]?.grid.rows; const idList = currentValue.map((item) => item.id);
if (!currentValue) return; const newIdOrder = dndUtils.reorderById({
edge,
idFrom,
idTo,
list: idList,
});
const idList = currentValue.map((item) => item.id); // Map the new ID order back to full items
const newIdOrder = dndUtils.reorderById({ const newOrder = newIdOrder.map((id) => currentValue.find((item) => item.id === id)!);
edge, onChangeRef.current(newOrder);
idFrom, }, []);
idTo,
list: idList,
});
// Map the new ID order back to full items
const newOrder = newIdOrder.map((id) => currentValue.find((item) => item.id === id)!);
onChange(newOrder);
},
[listKey, onChange],
);
return ( return (
<Stack gap="xs"> <Stack gap="xs">
@@ -11,7 +11,7 @@ import {
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview'; import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
import clsx from 'clsx'; import clsx from 'clsx';
import Fuse, { type FuseResultMatch } from 'fuse.js'; import Fuse, { type FuseResultMatch } from 'fuse.js';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from './table-config.module.css'; import styles from './table-config.module.css';
@@ -252,10 +252,7 @@ export const TableConfig = ({
<Divider /> <Divider />
<TableColumnConfig <TableColumnConfig
data={tableColumnsData} data={tableColumnsData}
listKey={listKey} onChange={(columns) => setList(listKey, { table: { columns } })}
onChange={(columns) =>
setList(listKey, { ...list, table: { ...list.table, columns } })
}
value={list.table.columns} value={list.table.columns}
/> />
</> </>
@@ -264,17 +261,23 @@ export const TableConfig = ({
const TableColumnConfig = ({ const TableColumnConfig = ({
data, data,
listKey,
onChange, onChange,
value, value,
}: { }: {
data: { label: string; value: string }[]; data: { label: string; value: string }[];
listKey: ItemListKey;
onChange: (value: ItemTableListColumnConfig[]) => void; onChange: (value: ItemTableListColumnConfig[]) => void;
value: ItemTableListColumnConfig[]; value: ItemTableListColumnConfig[];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const valueRef = useRef(value);
const onChangeRef = useRef(onChange);
useLayoutEffect(() => {
valueRef.current = value;
onChangeRef.current = onChange;
});
const labelMap = useMemo(() => { const labelMap = useMemo(() => {
return data.reduce( return data.reduce(
(acc, item) => { (acc, item) => {
@@ -285,133 +288,97 @@ const TableColumnConfig = ({
); );
}, [data]); }, [data]);
const handleChangeEnabled = useCallback( const handleChangeEnabled = useCallback((item: ItemTableListColumnConfig, checked: boolean) => {
(item: ItemTableListColumnConfig, checked: boolean) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], isEnabled: checked };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], isEnabled: checked }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleMoveUp = useCallback( const handleMoveUp = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; if (index === 0) return;
const index = value.findIndex((v) => v.id === item.id); const newValues = [...currentValue];
if (index === 0) return; [newValues[index], newValues[index - 1]] = [newValues[index - 1], newValues[index]];
const newValues = [...value]; onChangeRef.current(newValues);
[newValues[index], newValues[index - 1]] = [newValues[index - 1], newValues[index]]; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleMoveDown = useCallback( const handleMoveDown = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; if (index === currentValue.length - 1) return;
const index = value.findIndex((v) => v.id === item.id); const newValues = [...currentValue];
if (index === value.length - 1) return; [newValues[index], newValues[index + 1]] = [newValues[index + 1], newValues[index]];
const newValues = [...value]; onChangeRef.current(newValues);
[newValues[index], newValues[index + 1]] = [newValues[index + 1], newValues[index]]; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handlePinToLeft = useCallback( const handlePinToLeft = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id);
const newValues = [...value];
const isPinned = newValues[index].pinned; const isPinned = newValues[index].pinned;
const isPinnedLeft = isPinned === 'left'; const isPinnedLeft = isPinned === 'left';
if (isPinnedLeft) { if (isPinnedLeft) {
newValues[index] = { ...newValues[index], pinned: null }; newValues[index] = { ...newValues[index], pinned: null };
} else { } else {
newValues[index] = { ...newValues[index], pinned: 'left' }; newValues[index] = { ...newValues[index], pinned: 'left' };
} }
onChange(newValues); onChangeRef.current(newValues);
}, }, []);
[listKey, onChange],
);
const handlePinToRight = useCallback( const handlePinToRight = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id);
const newValues = [...value];
const isPinned = newValues[index].pinned; const isPinned = newValues[index].pinned;
const isPinnedRight = isPinned === 'right'; const isPinnedRight = isPinned === 'right';
if (isPinnedRight) { if (isPinnedRight) {
newValues[index] = { ...newValues[index], pinned: null }; newValues[index] = { ...newValues[index], pinned: null };
} else { } else {
newValues[index] = { ...newValues[index], pinned: 'right' }; newValues[index] = { ...newValues[index], pinned: 'right' };
} }
onChange(newValues); onChangeRef.current(newValues);
}, }, []);
[listKey, onChange],
);
const handleAlignLeft = useCallback( const handleAlignLeft = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], align: 'start' };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], align: 'start' }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleAlignCenter = useCallback( const handleAlignCenter = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], align: 'center' };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], align: 'center' }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleAlignRight = useCallback( const handleAlignRight = useCallback((item: ItemTableListColumnConfig) => {
(item: ItemTableListColumnConfig) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], align: 'end' };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], align: 'end' }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleAutoSize = useCallback( const handleAutoSize = useCallback((item: ItemTableListColumnConfig, checked: boolean) => {
(item: ItemTableListColumnConfig, checked: boolean) => { const currentValue = valueRef.current;
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const index = currentValue.findIndex((v) => v.id === item.id);
if (!value) return; const newValues = [...currentValue];
const index = value.findIndex((v) => v.id === item.id); newValues[index] = { ...newValues[index], autoSize: checked };
const newValues = [...value]; onChangeRef.current(newValues);
newValues[index] = { ...newValues[index], autoSize: checked }; }, []);
onChange(newValues);
},
[listKey, onChange],
);
const handleRowWidth = useCallback( const handleRowWidth = useCallback(
(item: ItemTableListColumnConfig, number: number | string) => { (item: ItemTableListColumnConfig, number: number | string) => {
@@ -427,14 +394,13 @@ const TableColumnConfig = ({
number = 2000; number = 2000;
} }
const value = useSettingsStore.getState().lists[listKey]?.table.columns; const currentValue = valueRef.current;
if (!value) return; const index = currentValue.findIndex((v) => v.id === item.id);
const index = value.findIndex((v) => v.id === item.id); const newValues = [...currentValue];
const newValues = [...value];
newValues[index] = { ...newValues[index], width: number }; newValues[index] = { ...newValues[index], width: number };
onChange(newValues); onChangeRef.current(newValues);
}, },
[listKey, onChange], [],
); );
const [searchColumns, setSearchColumns] = useDebouncedState('', 300); const [searchColumns, setSearchColumns] = useDebouncedState('', 300);
@@ -465,25 +431,20 @@ const TableColumnConfig = ({
})); }));
}, [value, searchColumns, fuse]); }, [value, searchColumns, fuse]);
const handleReorder = useCallback( const handleReorder = useCallback((idFrom: string, idTo: string, edge: Edge | null) => {
(idFrom: string, idTo: string, edge: Edge | null) => { const currentValue = valueRef.current;
const currentValue = useSettingsStore.getState().lists[listKey]?.table.columns; const idList = currentValue.map((item) => item.id);
if (!currentValue) return; const newIdOrder = dndUtils.reorderById({
edge,
idFrom,
idTo,
list: idList,
});
const idList = currentValue.map((item) => item.id); // Map the new ID order back to full items
const newIdOrder = dndUtils.reorderById({ const newOrder = newIdOrder.map((id) => currentValue.find((item) => item.id === id)!);
edge, onChangeRef.current(newOrder);
idFrom, }, []);
idTo,
list: idList,
});
// Map the new ID order back to full items
const newOrder = newIdOrder.map((id) => currentValue.find((item) => item.id === id)!);
onChange(newOrder);
},
[listKey, onChange],
);
return ( return (
<Stack gap="xs"> <Stack gap="xs">