fix: stop the template picker from raising a React key error in development

This commit is contained in:
Linus Rath
2026-10-28 10:41:44 +01:00
parent 768f3fe19b
commit f80f6b0bf7
2 changed files with 23 additions and 5 deletions
@@ -1,5 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useTemplateStore } from '@/stores/template-store';
import type { EmailTemplate } from '@/lib/template-types';
// The template picker offers "Manage templates", so making or fixing a
// template doesn't need a trip to Settings in the middle of a message.
@@ -19,4 +21,19 @@ describe('TemplatePicker', () => {
fireEvent.click(screen.getByText('done'));
expect(screen.getByRole('dialog', { name: 'picker_title' })).toBeTruthy();
});
// The sections (favorites, recent, each category) are a list: without
// keys React warned, which the dev build shows as an error overlay.
it('renders its sections without a React key warning', () => {
const template = (id: string, category: string, isFavorite = false): EmailTemplate => ({
id, name: `T ${id}`, subject: '', body: '', category, isFavorite,
createdAt: '2026-10-01T00:00:00Z', updatedAt: '2026-10-01T00:00:00Z', usageCount: 0,
} as EmailTemplate);
useTemplateStore.setState({ templates: [template('a', 'Work', true), template('b', 'Work'), template('c', 'Home'), template('d', '')] });
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
render(<TemplatePicker isOpen onClose={vi.fn()} onSelect={vi.fn()} />);
expect(screen.getByText('T c')).toBeTruthy();
expect(error.mock.calls.some((call) => String(call[0]).includes('unique "key"'))).toBe(false);
error.mockRestore();
});
});
+6 -5
View File
@@ -97,10 +97,10 @@ export function TemplatePicker({ isOpen, onClose, onSelect }: TemplatePickerProp
</button>
);
const renderSection = (title: string, items: EmailTemplate[]) => {
const renderSection = (key: string, title: string, items: EmailTemplate[]) => {
if (items.length === 0) return null;
return (
<div className="mb-4">
<div key={key} className="mb-4">
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1 px-3">
{title}
</h3>
@@ -171,15 +171,16 @@ export function TemplatePicker({ isOpen, onClose, onSelect }: TemplatePickerProp
)
) : (
<>
{renderSection(t('section_favorites'), favorites)}
{renderSection(t('section_recent'), recentFiltered)}
{renderSection('favorites', t('section_favorites'), favorites)}
{renderSection('recent', t('section_recent'), recentFiltered)}
{categorizedEntries.map(([cat, items]) =>
renderSection(
`category:${cat}`,
cat,
items.filter((i) => !shownIds.has(i.id))
)
)}
{renderSection(t('section_uncategorized'), uncategorizedFiltered)}
{renderSection('uncategorized', t('section_uncategorized'), uncategorizedFiltered)}
</>
)}
</div>