Итак, я просто пытаюсь проверить эту форму с помощью 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
ZOD Проверка DTO – реакция – отсутствуют сообщения об ошибках ⇐ Javascript
Форум по Javascript
-
Anonymous
1762709952
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"}
)}
);
}
Подробнее здесь: [url]https://stackoverflow.com/questions/79814925/zod-validation-of-dto-react-missing-error-messages[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия