Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Displaying Validation Errors in the Form

Displaying Validation Errors in the Form

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

The violations array from chapter 20 has, SO FAR, ended up in the generic isError state from chapter 78 – this chapter connects it WITH the INDIVIDUAL form fields.

Defining the error type

src/types/error.ts
export interface Violation {
  propertyPath: string;
  message: string;
}

export interface ApiErrorResponse {
  violations?: Violation[];
}

Extracting violations from the axios error

src/hooks/useCreateProject.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { isAxiosError } from 'axios';
import { apiClient } from '../api/client';
import type { Project } from '../types/project';
import type { ApiErrorResponse, Violation } from '../types/error';

interface CreateProjectInput {
  name: string;
  description?: string;
}

export function useCreateProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (input: CreateProjectInput) => {
      const response = await apiClient.post<Project>('/projects', input);

      return response.data;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
    },
  });
}

export function extractViolations(error: unknown): Violation[] {
  if (isAxiosError<ApiErrorResponse>(error) && error.response?.data.violations) {
    return error.response.data.violations;
  }

  return [];
}

isAxiosError from axios itself is a TYPE GUARD that PROVES to TypeScript that error has the expected response.data structure – WITHOUT this guard, TypeScript would treat error as unknown and REFUSE EVERY field access.

Extending the form with error display

src/components/CreateProjectForm.tsx
import { useState, type FormEvent } from 'react';
import { useCreateProject, extractViolations } from '../hooks/useCreateProject';

function CreateProjectForm() {
  const [name, setName] = useState('');
  const createProject = useCreateProject();
  const violations = createProject.isError
    ? extractViolations(createProject.error)
    : [];

  function fieldError(field: string): string | undefined {
    return violations.find((v) => v.propertyPath === field)?.message;
  }

  function handleSubmit(event: FormEvent) {
    event.preventDefault();
    createProject.mutate({ name }, { onSuccess: () => setName('') });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      {fieldError('name') && <p className="error">{fieldError('name')}</p>}
      <button type="submit" disabled={createProject.isPending}>
        Create project
      </button>
    </form>
  );
}

export default CreateProjectForm;

fieldError('name') looks up SPECIFICALLY for ONE violation with propertyPath === 'name' – EXACTLY the mechanism chapter 20 already PREVIEWED: propertyPath is what makes the FIELD-PRECISE mapping POSSIBLE in the first place.

Achtung: #[UniqueProjectName] from chapter 26 delivers ITS message ALSO with propertyPath: 'name' – the form displays CUSTOM validation constraints AND CUSTOM business rules (uniqueness) in the SAME UI element, WITHOUT the frontend having to distinguish BETWEEN the two.

Tipp: React Hook Form (DELIBERATELY NOT used in this course, to show the BASIC mechanics without an additional library) would make this EXACT fieldError mechanism EVEN more ergonomic via setError(propertyPath, { message }), WITHOUT changing the underlying principle.