fix: Prevent double submission in Create Page dialog

- Add ref-based double submission protection (isSubmittingRef)
- Extract handleSubmit function with isPending checks
- Add loading spinner during submission
- Disable inputs during submission
- Suppress error toast for duplicate prevention errors
This commit is contained in:
Dwindi Ramadhana
2026-01-11 23:22:47 +07:00
parent fe243a42cb
commit e66f5e54a1

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useMutation } from '@tanstack/react-query';
import { api } from '@/lib/api';
import { __ } from '@/lib/i18n';
@@ -15,7 +15,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { toast } from 'sonner';
import { FileText, Layout } from 'lucide-react';
import { FileText, Layout, Loader2 } from 'lucide-react';
interface PageItem {
id?: number;
@@ -36,18 +36,30 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
const [title, setTitle] = useState('');
const [slug, setSlug] = useState('');
// Prevent double submission
const isSubmittingRef = useRef(false);
// Get site URL from WordPress config
const siteUrl = window.WNW_CONFIG?.siteUrl?.replace(/\/$/, '') || window.location.origin;
// Create page mutation
const createMutation = useMutation({
mutationFn: async () => {
if (pageType === 'page') {
const response = await api.post('/pages', { title, slug });
return response.data;
mutationFn: async (data: { title: string; slug: string }) => {
// Guard against double submission
if (isSubmittingRef.current) {
throw new Error('Request already in progress');
}
isSubmittingRef.current = true;
try {
const response = await api.post('/pages', { title: data.title, slug: data.slug });
return response.data;
} finally {
// Reset after a delay to prevent race conditions
setTimeout(() => {
isSubmittingRef.current = false;
}, 500);
}
// For templates, we don't create them - they're auto-created for each CPT
return null;
},
onSuccess: (data) => {
if (data?.page) {
@@ -64,6 +76,10 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
}
},
onError: (error: any) => {
// Don't show error for duplicate prevention
if (error?.message === 'Request already in progress') {
return;
}
// Extract error message from the response
const message = error?.response?.data?.message ||
error?.message ||
@@ -82,15 +98,28 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
}
};
// Handle form submission
const handleSubmit = () => {
if (createMutation.isPending || isSubmittingRef.current) {
return;
}
if (pageType === 'page' && title && slug) {
createMutation.mutate({ title, slug });
}
};
// Reset form when modal closes
useEffect(() => {
if (!open) {
setTitle('');
setSlug('');
setPageType('page');
isSubmittingRef.current = false;
}
}, [open]);
const isDisabled = pageType === 'page' && (!title || !slug) || createMutation.isPending || isSubmittingRef.current;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px]">
@@ -141,6 +170,7 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
value={title}
onChange={(e) => handleTitleChange(e.target.value)}
placeholder={__('e.g., About Us')}
disabled={createMutation.isPending}
/>
</div>
@@ -151,6 +181,7 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
value={slug}
onChange={(e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ''))}
placeholder={__('e.g., about-us')}
disabled={createMutation.isPending}
/>
<p className="text-xs text-muted-foreground">
{__('URL will be: ')}<span className="font-mono text-primary">{siteUrl}/{slug || 'page-slug'}</span>
@@ -161,14 +192,21 @@ export function CreatePageModal({ open, onOpenChange, onCreated }: CreatePageMod
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={createMutation.isPending}>
{__('Cancel')}
</Button>
<Button
onClick={() => createMutation.mutate()}
disabled={pageType === 'page' && (!title || !slug) || createMutation.isPending}
onClick={handleSubmit}
disabled={isDisabled}
>
{createMutation.isPending ? __('Creating...') : __('Create Page')}
{createMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
{__('Creating...')}
</>
) : (
__('Create Page')
)}
</Button>
</DialogFooter>
</DialogContent>