Fix: Prevent infinite re-renders.
- Addressed an issue where the component was re-rendering excessively, leading to performance problems.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -38,8 +38,9 @@ interface EditOperationalPageDialogProps {
|
|||||||
|
|
||||||
export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOperationalPageDialogProps) => {
|
export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOperationalPageDialogProps) => {
|
||||||
const [selectedComponents, setSelectedComponents] = useState<Partial<StatusPageComponentRecord>[]>([]);
|
const [selectedComponents, setSelectedComponents] = useState<Partial<StatusPageComponentRecord>[]>([]);
|
||||||
const [existingComponents, setExistingComponents] = useState<StatusPageComponentRecord[]>([]);
|
|
||||||
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
|
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
|
||||||
|
const [componentsLoaded, setComponentsLoaded] = useState(false);
|
||||||
|
|
||||||
const updateMutation = useUpdateOperationalPage();
|
const updateMutation = useUpdateOperationalPage();
|
||||||
const createComponentMutation = useCreateStatusPageComponent();
|
const createComponentMutation = useCreateStatusPageComponent();
|
||||||
const deleteComponentMutation = useDeleteStatusPageComponent();
|
const deleteComponentMutation = useDeleteStatusPageComponent();
|
||||||
@@ -63,28 +64,42 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
// Memoize the form reset values to prevent unnecessary re-renders
|
||||||
if (page) {
|
const formValues = useMemo(() => {
|
||||||
form.reset({
|
if (!page) return null;
|
||||||
title: page.title,
|
return {
|
||||||
description: page.description,
|
title: page.title,
|
||||||
slug: page.slug,
|
description: page.description,
|
||||||
theme: page.theme,
|
slug: page.slug,
|
||||||
status: page.status,
|
theme: page.theme,
|
||||||
is_public: page.is_public === 'true',
|
status: page.status,
|
||||||
logo_url: page.logo_url || '',
|
is_public: page.is_public === 'true',
|
||||||
custom_domain: page.custom_domain || '',
|
logo_url: page.logo_url || '',
|
||||||
custom_css: page.custom_css || '',
|
custom_domain: page.custom_domain || '',
|
||||||
page_style: page.page_style || '',
|
custom_css: page.custom_css || '',
|
||||||
});
|
page_style: page.page_style || '',
|
||||||
}
|
};
|
||||||
}, [page, form]);
|
}, [page?.id, page?.title, page?.description, page?.slug, page?.theme, page?.status, page?.is_public, page?.logo_url, page?.custom_domain, page?.custom_css, page?.page_style]);
|
||||||
|
|
||||||
|
// Reset form when page data changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (components && components.length > 0) {
|
if (formValues) {
|
||||||
console.log('Loading existing components:', components);
|
form.reset(formValues);
|
||||||
setExistingComponents(components);
|
}
|
||||||
// Convert existing components to the format expected by ComponentsSelector
|
}, [formValues, form]);
|
||||||
|
|
||||||
|
// Convert components to selector format and initialize state - only when dialog opens and components change
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || !page?.id || !components) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update if components actually changed or haven't been loaded yet
|
||||||
|
const componentIds = components.map(c => c.id).sort().join(',');
|
||||||
|
const currentSelectedIds = selectedComponents.map(c => c.id).filter(Boolean).sort().join(',');
|
||||||
|
|
||||||
|
if (componentIds !== currentSelectedIds || !componentsLoaded) {
|
||||||
|
// console.log('Loading existing components:', components);
|
||||||
const existingComponentsForSelector = components.map(comp => ({
|
const existingComponentsForSelector = components.map(comp => ({
|
||||||
id: comp.id,
|
id: comp.id,
|
||||||
name: comp.name,
|
name: comp.name,
|
||||||
@@ -94,25 +109,31 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
|||||||
display_order: comp.display_order,
|
display_order: comp.display_order,
|
||||||
operational_status_id: comp.operational_status_id,
|
operational_status_id: comp.operational_status_id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setSelectedComponents(existingComponentsForSelector);
|
setSelectedComponents(existingComponentsForSelector);
|
||||||
} else {
|
setComponentsLoaded(true);
|
||||||
setExistingComponents([]);
|
}
|
||||||
|
}, [open, page?.id, components, componentsLoaded, selectedComponents]);
|
||||||
|
|
||||||
|
// Reset state when dialog closes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
setComponentsLoaded(false);
|
||||||
setSelectedComponents([]);
|
setSelectedComponents([]);
|
||||||
}
|
}
|
||||||
}, [components]);
|
}, [open]);
|
||||||
|
|
||||||
const handleComponentDelete = async (componentId: string) => {
|
const handleComponentDelete = useCallback(async (componentId: string) => {
|
||||||
try {
|
try {
|
||||||
console.log('Deleting component:', componentId);
|
// console.log('Deleting component:', componentId);
|
||||||
await deleteComponentMutation.mutateAsync(componentId);
|
await deleteComponentMutation.mutateAsync(componentId);
|
||||||
|
|
||||||
// Update local state to remove the deleted component
|
// Update local state to remove the deleted component
|
||||||
setSelectedComponents(prev => prev.filter(comp => comp.id !== componentId));
|
setSelectedComponents(prev => prev.filter(comp => comp.id !== componentId));
|
||||||
setExistingComponents(prev => prev.filter(comp => comp.id !== componentId));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting component:', error);
|
// console.error('Error deleting component:', error);
|
||||||
}
|
}
|
||||||
};
|
}, [deleteComponentMutation]);
|
||||||
|
|
||||||
const onSubmit = async (data: FormData) => {
|
const onSubmit = async (data: FormData) => {
|
||||||
if (!page) return;
|
if (!page) return;
|
||||||
@@ -133,21 +154,17 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
|||||||
page_style: data.page_style || '',
|
page_style: data.page_style || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Updating operational page with payload:', payload);
|
// console.log('Updating operational page with payload:', payload);
|
||||||
await updateMutation.mutateAsync({ id: page.id, data: payload });
|
await updateMutation.mutateAsync({ id: page.id, data: payload });
|
||||||
|
|
||||||
// Handle component changes
|
// Handle component changes
|
||||||
const currentComponentIds = existingComponents.map(c => c.id);
|
const currentComponentIds = components.map(c => c.id);
|
||||||
const newComponentsToCreate = selectedComponents.filter(comp => !comp.id);
|
const newComponentsToCreate = selectedComponents.filter(comp => !comp.id);
|
||||||
const componentsToKeep = selectedComponents.filter(comp => comp.id && currentComponentIds.includes(comp.id));
|
const componentsToDelete = components.filter(comp => !selectedComponents.some(selected => selected.id === comp.id));
|
||||||
const componentsToDelete = existingComponents.filter(comp => !selectedComponents.some(selected => selected.id === comp.id));
|
|
||||||
|
|
||||||
// Delete removed components (only if not already deleted via handleComponentDelete)
|
// Delete removed components
|
||||||
for (const component of componentsToDelete) {
|
for (const component of componentsToDelete) {
|
||||||
if (selectedComponents.some(selected => selected.id === component.id)) {
|
// console.log('Deleting component during save:', component.id);
|
||||||
continue; // Skip if already handled by handleComponentDelete
|
|
||||||
}
|
|
||||||
console.log('Deleting component during save:', component.id);
|
|
||||||
await deleteComponentMutation.mutateAsync(component.id);
|
await deleteComponentMutation.mutateAsync(component.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,13 +179,13 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
|||||||
display_order: component.display_order || 1,
|
display_order: component.display_order || 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Creating component with payload:', componentPayload);
|
// console.log('Creating component with payload:', componentPayload);
|
||||||
await createComponentMutation.mutateAsync(componentPayload);
|
await createComponentMutation.mutateAsync(componentPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating operational page:', error);
|
// console.error('Error updating operational page:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsFormSubmitting(false);
|
setIsFormSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user