farion1231/cc-switch · warning · PiFormValidationError

pi.form.selectPresetRequired

Error message

pi.form.selectPresetRequired

What it means

Thrown by the Pi provider form's submit() in CC Switch (PiProviderForm.tsx:1067-1069) when saving a NEW Pi provider while selectedPresetId is still null. New Pi providers derive their transport defaults and template values from a built-in preset (or the explicit 'custom' entry), so creation without a selection is rejected. It is a PiFormValidationError with no fieldSelector: the catch block (lines 1254-1285) renders it as an inline form error plus a toast, and nothing is saved.

Source

Thrown at src/components/providers/forms/PiProviderForm.tsx:1068

      });
      if (!applied) return;
      includeCompatRef.current = includeCompat;
      setProviderCompat(value);
    },
    [updateSettingsConfig],
  );

  const handleProviderKeyChange = useCallback((value: string) => {
    const normalized = value.toLowerCase().replace(/[^a-z0-9-]/g, "");
    setProviderKey(normalized);
  }, []);

  const submit = async (identity: ProviderFormData) => {
    onSubmittingChange?.(true);
    setFormError(null);
    try {
      if (!isEdit && selectedPresetId === null) {
        throw new PiFormValidationError(t("pi.form.selectPresetRequired"));
      }
      if (!parseJsonObject(identity.settingsConfig)) {
        throw new PiFormValidationError(
          t("jsonEditor.mustBeObject"),
          "#pi-settings-config",
        );
      }
      const trimmedName = identity.name.trim();
      const trimmedKey = providerKey.trim();
      if (!trimmedName) {
        throw new PiFormValidationError(
          t("pi.form.nameRequired"),
          'input[name="name"]',
        );
      }
      if (!isEdit && !trimmedKey) {
        throw new PiFormValidationError(
          t("pi.form.providerKeyRequired"),

View on GitHub (pinned to a2e22f3302)

Solutions

  1. Select a preset (or 'Custom') in the preset picker, then save again
  2. Initialize selectedPresetId to a sensible default (first preset or 'custom') when the dialog opens in create mode
  3. Disable the Save button while !isEdit && selectedPresetId === null, mirroring the form's own hasConfigurationSelection check (PiProviderForm.tsx:540)
  4. In tests, drive the preset selector (or inject state) so selectedPresetId is non-null before submitting

Example fix

// before (dialog opens with no default)
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(null);

// after (create mode defaults to Custom, matching the guard's semantics)
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
  isEdit ? null : "custom",
);
Defensive patterns

Strategy: validation

Validate before calling

// Before invoking submit for a new Pi provider:
if (!isEdit && selectedPresetId === null) {
  setFormError(t("pi.form.selectPresetRequired"));
  return; // don't call submit
}

Try / catch

try {
  await submit(identity);
} catch (error) {
  if (error instanceof Error && error.name === "PiFormValidationError") {
    // expected inline validation error; already surfaced by the form
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling submit() with isEdit=false and selectedPresetId===null: the user opened the 'Add provider' dialog and clicked Save without ever touching ProviderPresetSelector, or the useState holding the selection was reset by a remount/HMR before submit.

Common situations: Hitting Save immediately after opening the create dialog; embedding PiProviderForm in a custom wrapper that skips the preset selector; React StrictMode double-mount losing uncommitted picker state; automated tests submitting the form programmatically without selecting a preset.

Related errors


AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16). Data as JSON: /api/errors/821dc89609cefea9. Report an issue: GitHub.