fix(provider-form): prevent duplicate submissions on rapid button clicks (#1352)

Make ProviderForm.handleSubmit async and await onSubmit so react-hook-form's
isSubmitting state is tracked. Disable submit buttons in AddProviderDialog and
EditProviderDialog while submission is in flight via onSubmittingChange callback.
This commit is contained in:
Hexi
2026-03-15 15:24:17 +08:00
committed by GitHub
parent d8a7bc32db
commit 5c03de53f7
3 changed files with 18 additions and 4 deletions
@@ -48,6 +48,7 @@ export function AddProviderDialog({
const [universalFormOpen, setUniversalFormOpen] = useState(false);
const [selectedUniversalPreset, setSelectedUniversalPreset] =
useState<UniversalProviderPreset | null>(null);
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
const handleUniversalProviderSave = useCallback(
async (provider: UniversalProvider) => {
@@ -247,6 +248,7 @@ export function AddProviderDialog({
<Button
type="submit"
form="provider-form"
disabled={isFormSubmitting}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
<Plus className="h-4 w-4 mr-2" />
@@ -299,6 +301,7 @@ export function AddProviderDialog({
submitLabel={t("common.add")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
onSubmittingChange={setIsFormSubmitting}
showButtons={false}
/>
</TabsContent>
@@ -314,6 +317,7 @@ export function AddProviderDialog({
submitLabel={t("common.add")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
onSubmittingChange={setIsFormSubmitting}
showButtons={false}
/>
)}
@@ -28,6 +28,7 @@ export function EditProviderDialog({
isProxyTakeover = false,
}: EditProviderDialogProps) {
const { t } = useTranslation();
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
// 默认使用传入的 provider.settingsConfig,若当前编辑对象是"当前生效供应商",则尝试读取实时配置替换初始值
const [liveSettings, setLiveSettings] = useState<Record<
@@ -197,6 +198,7 @@ export function EditProviderDialog({
<Button
type="submit"
form="provider-form"
disabled={isFormSubmitting}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
<Save className="h-4 w-4 mr-2" />
@@ -210,6 +212,7 @@ export function EditProviderDialog({
submitLabel={t("common.save")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
onSubmittingChange={setIsFormSubmitting}
initialData={initialData}
showButtons={false}
/>
@@ -104,10 +104,11 @@ interface ProviderFormProps {
appId: AppId;
providerId?: string;
submitLabel: string;
onSubmit: (values: ProviderFormValues) => void;
onSubmit: (values: ProviderFormValues) => Promise<void> | void;
onCancel: () => void;
onUniversalPresetSelect?: (preset: UniversalProviderPreset) => void;
onManageUniversalProviders?: () => void;
onSubmittingChange?: (isSubmitting: boolean) => void;
initialData?: {
name?: string;
websiteUrl?: string;
@@ -129,6 +130,7 @@ export function ProviderForm({
onCancel,
onUniversalPresetSelect,
onManageUniversalProviders,
onSubmittingChange,
initialData,
showButtons = true,
}: ProviderFormProps) {
@@ -237,6 +239,7 @@ export function ProviderForm({
defaultValues,
mode: "onSubmit",
});
const { isSubmitting } = form.formState;
const handleSettingsConfigChange = useCallback(
(config: string) => {
@@ -257,6 +260,10 @@ export function ProviderForm({
},
);
useEffect(() => {
onSubmittingChange?.(isSubmitting);
}, [isSubmitting, onSubmittingChange]);
const {
apiKey,
handleApiKeyChange,
@@ -583,7 +590,7 @@ export function ProviderForm({
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
const handleSubmit = (values: ProviderFormData) => {
const handleSubmit = async (values: ProviderFormData) => {
if (appId === "claude" && templateValueEntries.length > 0) {
const validation = validateTemplateValues();
if (!validation.isValid && validation.missingField) {
@@ -892,7 +899,7 @@ export function ProviderForm({
: undefined,
};
onSubmit(payload);
await onSubmit(payload);
};
const groupedPresets = useMemo(() => {
@@ -1600,7 +1607,7 @@ export function ProviderForm({
<Button variant="outline" type="button" onClick={onCancel}>
{t("common.cancel")}
</Button>
<Button type="submit">{submitLabel}</Button>
<Button type="submit" disabled={isSubmitting}>{submitLabel}</Button>
</div>
)}
</form>