fix: use Index instead of For loop for webhook header inputs (addresses #412)

Switched from For to Index loop which provides better stability for input elements in SolidJS. Also removed circular dependency between headerInputs and formData that was causing re-renders.
This commit is contained in:
Pulse Monitor
2025-09-04 20:09:38 +00:00
parent 0f48b90adf
commit 5ae54019ba
@@ -1,4 +1,4 @@
import { createSignal, createEffect, Show, For } from 'solid-js';
import { createSignal, createEffect, Show, For, Index } from 'solid-js';
import { NotificationsAPI, Webhook } from '@/api/notifications';
interface WebhookTemplate {
@@ -38,34 +38,6 @@ export function WebhookConfig(props: WebhookConfigProps) {
// Track header inputs separately to avoid focus loss
const [headerInputs, setHeaderInputs] = createSignal<Array<{id: string; key: string; value: string}>>([]);
// Sync headerInputs with formData.headers
createEffect(() => {
const headers = formData().headers || {};
const currentInputs = headerInputs();
// Only update if we're starting fresh (not already editing headers)
if (currentInputs.length === 0 && Object.keys(headers).length > 0) {
setHeaderInputs(
Object.entries(headers).map(([key, value], index) => ({
id: `header-${Date.now()}-${index}`,
key,
value
}))
);
}
});
// Update formData.headers when headerInputs change
const updateHeadersInFormData = () => {
const headers: Record<string, string> = {};
headerInputs().forEach(input => {
if (input.key) {
headers[input.key] = input.value;
}
});
setFormData({ ...formData(), headers });
};
// Load webhook templates
createEffect(async () => {
try {
@@ -77,16 +49,22 @@ export function WebhookConfig(props: WebhookConfigProps) {
});
const saveWebhook = () => {
// First sync headers from inputs to formData
updateHeadersInFormData();
const data = formData();
if (!data.name || !data.url) return;
// Build headers from headerInputs
const headers: Record<string, string> = {};
headerInputs().forEach(input => {
if (input.key) {
headers[input.key] = input.value;
}
});
if (editingId()) {
props.onUpdate({
...data,
id: editingId()!,
headers,
service: data.service,
template: data.payloadTemplate
});
@@ -99,7 +77,7 @@ export function WebhookConfig(props: WebhookConfigProps) {
name: data.name,
url: data.url,
method: data.method,
headers: data.headers,
headers,
enabled: data.enabled,
service: data.service,
template: data.payloadTemplate
@@ -431,29 +409,33 @@ export function WebhookConfig(props: WebhookConfigProps) {
</span>
</label>
<div class="space-y-2">
<For each={headerInputs()}>
{(header) => (
<Index each={headerInputs()}>
{(header, index) => (
<div class="flex gap-2">
<input
type="text"
value={header.key}
value={header().key}
onInput={(e) => {
const newKey = e.currentTarget.value;
setHeaderInputs(inputs =>
inputs.map(h => h.id === header.id ? { ...h, key: newKey } : h)
);
setHeaderInputs(inputs => {
const newInputs = [...inputs];
newInputs[index] = { ...newInputs[index], key: newKey };
return newInputs;
});
}}
placeholder="Header Name"
class="flex-1 px-3 py-2 text-sm border rounded-lg dark:bg-gray-700 dark:border-gray-600"
/>
<input
type="text"
value={header.value}
value={header().value}
onInput={(e) => {
const newValue = e.currentTarget.value;
setHeaderInputs(inputs =>
inputs.map(h => h.id === header.id ? { ...h, value: newValue } : h)
);
setHeaderInputs(inputs => {
const newInputs = [...inputs];
newInputs[index] = { ...newInputs[index], value: newValue };
return newInputs;
});
}}
placeholder="Header Value"
class="flex-1 px-3 py-2 text-sm border rounded-lg dark:bg-gray-700 dark:border-gray-600"
@@ -461,7 +443,7 @@ export function WebhookConfig(props: WebhookConfigProps) {
<button
type="button"
onClick={() => {
setHeaderInputs(inputs => inputs.filter(h => h.id !== header.id));
setHeaderInputs(inputs => inputs.filter((_, i) => i !== index));
}}
class="px-3 py-2 text-sm text-red-600 hover:text-red-700 dark:text-red-400 border border-red-300 dark:border-red-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20"
>
@@ -469,7 +451,7 @@ export function WebhookConfig(props: WebhookConfigProps) {
</button>
</div>
)}
</For>
</Index>
<button
type="button"
onClick={() => {
@@ -513,9 +495,16 @@ export function WebhookConfig(props: WebhookConfigProps) {
<button
onClick={() => {
// Test the webhook with current form data
// Build headers from headerInputs
const headers: Record<string, string> = {};
headerInputs().forEach(input => {
if (input.key) {
headers[input.key] = input.value;
}
});
// Use a consistent temporary ID for this form session
const tempId = editingId() || 'temp-new-webhook';
props.onTest(tempId, formData());
props.onTest(tempId, { ...formData(), headers });
}}
disabled={props.testing === (editingId() || 'temp-new-webhook')}
class="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"