ZOD Проверка DTO – реакция – отсутствуют сообщения об ошибкахJavascript

Форум по Javascript
Ответить
Anonymous
 ZOD Проверка DTO – реакция – отсутствуют сообщения об ошибках

Сообщение Anonymous »

Итак, я просто пытаюсь проверить эту форму с помощью zod, которая действительно работает), но проблема в том, что она не отображает сообщения об ошибках должным образом (просто ничего не отображает, если поле недействительно). Форма использует форму tanstack в настройке машинописного текста. Я понятия не имею, почему это не работает. Любой ввод приветствуется.
Схема
import z from "zod";
import { zodErrorMessages } from "./../../../apps/web/src/shared/utils/zodErrorMessages";

// --- Sub-Schemas ---
export const createRecipeIngredientSchema = z.object({
ingredientId: z.number({ error: zodErrorMessages.requiredInput }).int().positive(),
quantity: z.string({ error: zodErrorMessages.requiredInput }).min(1).trim(),
unitId: z.number({ error: zodErrorMessages.requiredInput }).int().positive(),
note: z.string().trim().nullable(),
});

export const createRecipeStepSchema = z.object({
stepNumber: z.number({ error: zodErrorMessages.requiredInput }).int().positive(),
instruction: z.string({ error: zodErrorMessages.requiredInput }).min(1).trim(),
duration: z.number({ error: zodErrorMessages.requiredInput }).int().min(0),
});

export const createTagSchema = z.object({
tagId: z.number({ error: zodErrorMessages.requiredInput }).int().positive(),
});

// --- Haupt-Schema ---
export const createRecipeSchema = z.object({
title: z.string({ error: zodErrorMessages.requiredInput }).min(1).trim(),
description: z.string({ error: zodErrorMessages.requiredInput }).min(10).trim(),
prepTime: z.number({ error: zodErrorMessages.requiredInput }).int().min(0),
cookTime: z.number({ error: zodErrorMessages.requiredInput }).int().min(0),
servings: z.number({ error: zodErrorMessages.requiredInput }).int().min(1),
tags: z.array(createTagSchema),
ingredients: z.array(createRecipeIngredientSchema).min(1),
steps: z.array(createRecipeStepSchema).min(1),
});

// --- DTO-Typ ableiten ---
export type CreateRecipeDTO = z.infer;

// --- Sub-DTOs ---
export type CreateRecipeStepDTO = z.infer;
export type CreateRecipeIngredientDTO = z.infer;
export type CreateTagDTO = z.infer;


Форма
import { useForm } from "@tanstack/react-form";
import { useNavigate, Link } from "@tanstack/react-router";
import type {
CreateRecipeDTO,
CreateRecipeStepDTO,
CreateRecipeIngredientDTO,
} from "@repo/types/recipe";
import { createRecipe } from "./recipeCreate.service";
import { LoadingOverlay } from "../../../shared/components/other/loadingoverlay";
import { useState } from "react";
import { createRecipeSchema } from '../../../../../../packages/types/src/createRecipe.schema';

export const RecipeCreate = () => {
const navigate = useNavigate();
const [isSubmitting, setIsSubmitting] = useState(false); // State für Lade-Overlay während des Submit

const form = useForm({
defaultValues: {
title: "",
description: "",
prepTime: undefined,
cookTime: undefined,
servings: undefined,
tags: [],
authors: [],
steps: [{ stepNumber: 1, instruction: "", duration: 0 }],
ingredients: [],
} as CreateRecipeDTO, // Typen für das Formular setzen

validators: {
onSubmit: createRecipeSchema, // Zod-Schema validiert die Formulardaten (mag kein optional wegen dem DTO)
},

onSubmit: async ({ value }) => {
try {
// Die Formularwerte wurden erfolgreich validiert
setIsSubmitting(true); // Ladeanimation aktivieren
await createRecipe(value); // API-Call zum Erstellen des Rezepts
navigate({ to: "/recipes" }); // nach Erfolg weiterleiten
} catch (err) {
setIsSubmitting(false); // Fehler => Ladeanimation deaktivieren
}
},
});

return (

{/* Lade-Animation */}
{isSubmitting && (

)}


New Recipe
{
e.preventDefault();
e.stopPropagation();
await form.handleSubmit();
}}
className="space-y-6"
>
{/* Basis-Informationen */}

Basics


{/* Title Field */}

{(field) => (


Title (min. 1 character)

field.handleChange(e.target.value)}
onBlur={field.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}


{/* Description Field */}

{(field) => (


Description (min. 10 characters)

field.handleChange(e.target.value)}
onBlur={field.handleBlur}
rows={3}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>
{/* Zeichen-Zähler */}

{field.state.value?.length ?? 0} / 10


)}


{/* Time and Servings Fields */}

{/* PREPERATION Field (Name korrigiert) */}

{(field) => (


Preparation (Min)


// Parsen in Zahl oder undefined
field.handleChange(e.target.value ? parseInt(e.target.value) : undefined)
}
onBlur={field.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}


{/* COOK Field (Name korrigiert) */}

{(field) => (


Cook (Min)


field.handleChange(e.target.value ? parseInt(e.target.value) : undefined)
}
onBlur={field.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}


{/* Servings Field */}

{(field) => (


Servings


field.handleChange(e.target.value ? parseInt(e.target.value) : undefined)
}
onBlur={field.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}





{/* Zutaten */}


{(field) => (



Ingredients
{/* Fehleranzeige für die Liste (z.B. Mindestanzahl) reaktiviert */}


field.pushValue({
ingredientId: 0,
quantity: "",
unitId: 0,
note: null,
} as CreateRecipeIngredientDTO)
}
className="rounded bg-secondary px-4 py-2 font-medium text-text-dark"
>
+ Add Ingredient



{field.state.value?.length === 0 && (

No ingredients added yet. Click "Add Ingredient" to start.

)}

{field.state.value?.map((_, index) => (

{/* Erste Zeile: Ingredient ID + Quantity */}


{(subField) => (


Ingredient ID


subField.handleChange(parseInt(e.target.value) || 0)
}
onBlur={subField.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}



{(subField) => (


Quantity

subField.handleChange(e.target.value)}
onBlur={subField.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}



{/* Zweite Zeile: Unit ID + Note */}


{(subField) => (


Unit ID


subField.handleChange(parseInt(e.target.value) || 0)
}
onBlur={subField.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}



{(subField) => (


Note (optional)


// Setzt null, wenn das Feld leer ist
subField.handleChange(e.target.value || null)
}
onBlur={subField.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}



{/* Remove Button */}

field.removeValue(index)}
className="rounded bg-red-100 px-4 py-2 text-sm text-red-700"
>
Remove Ingredient



))}


)}



{/* Schritte */}


{(field) => (



Steps

{
const newStepNumber = (field.state.value?.length || 0) + 1;
field.pushValue({
stepNumber: newStepNumber,
instruction: "",
duration: 0,
} as CreateRecipeStepDTO);
}}
className="rounded bg-secondary px-4 py-2 font-medium text-text-dark"
>
+ Add Step



{field.state.value?.map((step, index) => (


{step.stepNumber}


{(subField) => (

subField.handleChange(e.target.value)}
onBlur={subField.handleBlur}
rows={2}
placeholder="Add instruction details... *"
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}



{(subField) => (


subField.handleChange(parseInt(e.target.value) || 0)
}
onBlur={subField.handleBlur}
className="w-full rounded border border-border px-3 py-2 focus:border-primary focus:outline-none"
/>

)}


{(field.state.value?.length || 0) > 1 && (
{
field.removeValue(index);
// Renumber remaining steps
const currentSteps = field.state.value || [];
const updatedSteps = currentSteps
.filter((_, i) => i !== index)
.map((step, i) => ({
...step,
stepNumber: i + 1,
}));
field.setValue(updatedSteps);
}}
className="rounded bg-red-100 px-4 py-2 text-red-700"
>
×

)}

))}


)}



{/* Buttons */}


Cancel

state.isSubmitting}>
{(isSubmitting) => (

{isSubmitting ? "Creating..." : "Create"}

)}





);
}



Подробнее здесь: https://stackoverflow.com/questions/798 ... r-messages
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Javascript»