fix(lists): prevent duplicate list item rendering on create
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s

This commit is contained in:
sandrodach committed 2026-10-25 20:05:43 +01:00
1 parent 5cfa390ee0
commit f1af10ce1c
2 files changed
+43 -15

No files matched your search

+41 -13
View File
@@ -64,12 +64,25 @@ export function getItemDisplayContent(item: ListItem): string {
return item.content;
}
export function deduplicateItems(items: ListItem[]): ListItem[] {
const seen = new Set<string>();
const result: ListItem[] = [];
for (const item of items) {
if (item && item.id && !seen.has(item.id)) {
seen.add(item.id);
result.push(item);
}
}
return result;
}
export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGlobal, currentUser }) => {
const [lists, setLists] = useState<SharedList[]>([]);
const [activeListId, setActiveListId] = useState<string>('');
const [newItemText, setNewItemText] = useState('');
const [newItemQty, setNewItemQty] = useState('');
const [isSubmittingItem, setIsSubmittingItem] = useState(false);
const [wsConnected, setWsConnected] = useState(false);
const [showNewListModal, setShowNewListModal] = useState(false);
@@ -103,9 +116,13 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
// fallback
}
}
setLists(all);
if (all.length > 0 && (!activeListId || !all.some((l) => l.id === activeListId))) {
setActiveListId(all[0].id);
const deduplicated = all.map((l) => ({
...l,
items: deduplicateItems(l.items || []),
}));
setLists(deduplicated);
if (deduplicated.length > 0 && (!activeListId || !deduplicated.some((l) => l.id === activeListId))) {
setActiveListId(deduplicated[0].id);
}
} catch {
// ignore
@@ -152,11 +169,12 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
const msgListId = msg.listId || (msg as any).list_id;
if (msg.event === 'ITEM_CREATED' && msgListId) {
const newItem: ListItem = msg.payload;
if (!newItem || !newItem.id) return;
setLists((prev) =>
prev.map((l) => {
if (l.id !== msgListId) return l;
if (l.items.some((i) => i.id === newItem.id)) return l;
return { ...l, items: [newItem, ...l.items] };
return { ...l, items: deduplicateItems([newItem, ...l.items]) };
})
);
} else if (msg.event === 'ITEM_TOGGLED' && msgListId) {
@@ -190,7 +208,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
msg.event === 'LIST_DELETED' ||
msg.event === 'LIST_SHARED' ||
msg.event === 'LIST_UNSHARED' ||
msg.event === 'ITEM_REORDERED'
msg.event === 'ITEM_REORDERED' ||
msg.event === 'LIST_UPDATED'
) {
loadLists();
}
@@ -216,7 +235,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
msg.event === 'LIST_UNSHARED' ||
msg.event === 'LIST_CREATED' ||
msg.event === 'LIST_DELETED' ||
msg.event === 'ITEM_REORDERED'
msg.event === 'ITEM_REORDERED' ||
msg.event === 'LIST_UPDATED'
) {
loadLists();
}
@@ -227,7 +247,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
const handleAddItem = async (e: React.FormEvent) => {
e.preventDefault();
if (!activeList || !newItemText.trim()) return;
if (!activeList || !newItemText.trim() || isSubmittingItem) return;
setIsSubmittingItem(true);
triggerHaptic(15);
setShowSuggestions(false);
@@ -239,11 +260,17 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
try {
const item = await api.createListItem(activeList.id, parsed.content, parsed.category, parsed.quantity);
setLists((prev) =>
prev.map((l) => (l.id === activeList.id ? { ...l, items: [item, ...l.items] } : l))
prev.map((l) => {
if (l.id !== activeList.id) return l;
if (l.items.some((i) => i.id === item.id)) return l;
return { ...l, items: deduplicateItems([item, ...l.items]) };
})
);
onRefreshGlobal();
} catch (err: any) {
alert(err.message || 'Fehler beim Erstellen des Listeneintrags');
} finally {
setIsSubmittingItem(false);
}
};
@@ -627,8 +654,9 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
}
};
const openItems = activeList?.items.filter((i) => !i.isCompleted) || [];
const completedItems = activeList?.items.filter((i) => i.isCompleted) || [];
const safeActiveItems = useMemo(() => deduplicateItems(activeList?.items || []), [activeList?.items]);
const openItems = useMemo(() => safeActiveItems.filter((i) => !i.isCompleted), [safeActiveItems]);
const completedItems = useMemo(() => safeActiveItems.filter((i) => i.isCompleted), [safeActiveItems]);
const groupedOpenItems = useMemo(() => {
const groupsMap = new Map<string, ListItem[]>();
@@ -690,7 +718,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
{/* List Tabs */}
<div className="flex flex-wrap items-center gap-1">
{lists.map((lst) => {
const openCount = lst.items.filter((i) => !i.isCompleted).length;
const openCount = deduplicateItems(lst.items || []).filter((i) => !i.isCompleted).length;
const isSelected = activeList?.id === lst.id;
return (
<div key={lst.id} className="relative group flex items-center">
@@ -909,10 +937,10 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
/>
<button
type="submit"
disabled={!newItemText.trim()}
disabled={isSubmittingItem || !newItemText.trim()}
className="btn-primary h-7 px-3 text-xs disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1 flex-shrink-0"
>
Hinzufügen
{isSubmittingItem ? 'Wird hinzugefügt…' : 'Hinzufügen'}
</button>
</div>
</form>