Использование dev tools

Интеграция YupResolver с инструментами разработчика значительно упрощает анализ ошибок валидации, контроль состояния формы и диагностику сложных сценариев. При использовании связки React Hook Form + Yup особую роль играют:

  • React DevTools
  • React Hook Form DevTools
  • браузерные инструменты разработчика
  • логирование схем Yup
  • анализ resolver-пайплайна
  • отслеживание ререндеров
  • профилирование производительности

Установка React Hook Form DevTools

Официальный пакет DevTools позволяет визуально отслеживать:

  • значения полей
  • ошибки
  • dirty state
  • touched state
  • состояние сабмита
  • регистрацию полей
  • работу resolver

Установка:

npm install @hookform/devtools

или:

yarn add @hookform/devtools

Подключение DevTools

Базовый пример:

import React from "react";
import { useForm } from "react-hook-form";
import { DevTool } from "@hookform/devtools";

function App() {
  const {
    register,
    control,
    handleSubmit
  } = useForm();

  return (
    <>
      <form onSub mit={handleSubmit(console.log)}>
        <input {...register("email")} />
        <button>Send</button>
      </form>

      <DevTool control={control} />
    </>
  );
}

После запуска приложения в интерфейсе появляется интерактивная панель состояния формы.


Использование YupResolver с DevTools

Типичная интеграция:

import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { DevTool } from "@hookform/devtools";
import * as yup from "yup";

const schema = yup.object({
  email: yup.string().email().required(),
  password: yup.string().min(6).required()
});

function App() {
  const {
    register,
    control,
    handleSubmit,
    formState
  } = useForm({
    resolver: yupResolver(schema)
  });

  return (
    <>
      <form onSub mit={handleSubmit(console.log)}>
        <input {...register("email")} />
        <input type="password" {...register("password")} />

        <button>Login</button>
      </form>

      <DevTool control={control} />
    </>
  );
}

Анализ состояния формы

DevTools показывает внутреннее состояние react-hook-form.

values

Текущие значения формы:

{
  "email": "admin@mail.com",
  "password": "123456"
}

Полезно для:

  • проверки синхронизации input
  • анализа controlled/uncontrolled компонентов
  • проверки defaultValues
  • поиска ошибок register

errors

Ошибки, возвращаемые YupResolver.

Пример:

{
  "email": {
    "message": "email must be a valid email"
  }
}

DevTools позволяет увидеть:

  • структуру объекта ошибок
  • путь вложенного поля
  • тип ошибки
  • момент появления ошибки

touchedFields

Показывает поля, с которыми уже взаимодействовали.

Пример:

{
  "email": true
}

Особенно полезно при:

  • условном отображении ошибок
  • реализации UX-валидации
  • сложной логике подсветки

dirtyFields

Отображает изменённые поля.

{
  "password": true
}

Помогает:

  • реализовывать кнопку “Сохранить”
  • отслеживать изменения
  • предотвращать потерю данных

isValid

Состояние общей валидности формы.

formState.isValid

Для корректной работы:

useForm({
  mode: "onChange",
  resolver: yupResolver(schema)
});

DevTools позволяет отслеживать обновление isValid в реальном времени.


Отладка Yup-схем

Логирование validate

Прямой вызов схемы:

schema.validate(data)
  .then(console.log)
  .catch(console.error);

Пример:

const data = {
  email: "wrong"
};

schema.validate(data)
  .catch(error => {
    console.log(error);
  });

Анализ ValidationError

Yup генерирует объект ValidationError.

catch(error => {
  console.log(error.path);
  console.log(error.message);
  console.log(error.errors);
});

Результат:

email
email must be a valid email
["email must be a valid email"]

Проверка всех ошибок одновременно

По умолчанию Yup останавливается на первой ошибке.

Для полного анализа:

schema.validate(data, {
  abortEarly: false
});

Пример:

const schema = yup.object({
  email: yup.string().email().required(),
  password: yup.string().min(8).required()
});

Результат:

[
  "email must be a valid email",
  "password must be at least 8 characters"
]

Отладка resolver

Проверка результата yupResolver

Resolver можно протестировать отдельно:

const resolver = yupResolver(schema);

resolver(
  {
    email: "wrong"
  },
  {},
  {}
).then(console.log);

Результат:

{
  values: {},
  errors: {
    email: {
      message: "email must be a valid email"
    }
  }
}

Анализ values и errors

Resolver возвращает:

{
  values,
  errors
}

Если валидация успешна:

{
  values: {
    email: "admin@mail.com"
  },
  errors: {}
}

При ошибке:

{
  values: {},
  errors: {
    email: {
      type: "email",
      message: "Invalid email"
    }
  }
}

Использование console.table

Для больших форм удобен табличный вывод:

console.table(formState.errors);

или:

console.table(getValues());

Пример:

field message
email Invalid email
password Too short

Анализ ререндеров

Избыточные ререндеры — частая проблема сложных форм.

Использование React DevTools

React DevTools показывает:

  • какие компоненты ререндерятся
  • почему произошёл ререндер
  • какие props изменились

Проверка количества ререндеров

console.count("Form render");

или:

function Input() {
  console.count("Input render");

  return <input />;
}

Memo и оптимизация

Проблемный пример:

<Input errors={errors} />

Объект errors меняется часто.

Оптимизация:

const error = errors.email;

<Input error={error} />

или:

export default React.memo(Input);

Отладка nested fields

Сложные структуры

const schema = yup.object({
  user: yup.object({
    profile: yup.object({
      email: yup.string().required()
    })
  })
});

DevTools показывает:

{
  "user": {
    "profile": {
      "email": {
        "message": "Required"
      }
    }
  }
}

Проверка путей

register("user.profile.email")

Ошибки пути — одна из самых распространённых проблем.


Debug mode через watch

Наблюдение за изменениями

const values = watch();

useEffect(() => {
  console.log(values);
}, [values]);

Отслеживание конкретного поля

const email = watch("email");

useEffect(() => {
  console.log(email);
}, [email]);

Использование getFieldState

Метод позволяет получать состояние отдельного поля.

const state = getFieldState("email");

console.log(state);

Результат:

{
  invalid: true,
  isDirty: true,
  isTouched: true,
  error: {
    message: "Invalid email"
  }
}

Проверка async validation

Асинхронная схема

const schema = yup.object({
  email: yup.string().test(
    "check-email",
    "Email already exists",
    async (value) => {
      const response = await fetch("/api/check");

      return response.ok;
    }
  )
});

Отладка async resolver

const onSub mit = async (data) => {
  console.log("before");

  await schema.validate(data);

  console.log("after");
};

При зависаниях проверяются:

  • network
  • Promise
  • fetch
  • race conditions
  • debounce

Анализ производительности

Проблемы больших схем

Тяжёлые схемы Yup могут:

  • тормозить ввод
  • вызывать лаги
  • создавать лишние validate cycle

Профилирование

React Profiler помогает определить:

  • дорогие компоненты
  • частоту validate
  • количество commit
  • продолжительность render phase

Оптимизация схем

Плохо:

const schema = yup.object({
  items: yup.array().of(
    yup.object({
      deep: yup.object({
        another: yup.object({
          field: yup.string().required()
        })
      })
    })
  )
});

Лучше разбивать:

const itemSchema = yup.object({
  field: yup.string().required()
});

const schema = yup.object({
  items: yup.array().of(itemSchema)
});

Debugging transform

Проверка transform

const schema = yup.string().transform(value => {
  console.log(value);

  return value.trim();
});

Анализ cast

schema.cast("  admin  ");

Результат:

"admin"

Debugging conditional validation

Проверка when

const schema = yup.object({
  role: yup.string(),

  company: yup.string().when("role", {
    is: "admin",
    then: schema => schema.required()
  })
});

Логирование значения

when("role", (role, schema) => {
  console.log(role);

  return schema.required();
});

Отладка FieldArray

Проверка массива

const schema = yup.object({
  users: yup.array().of(
    yup.object({
      email: yup.string().email()
    })
  )
});

DevTools показывает:

{
  "users": [
    {
      "email": "wrong"
    }
  ]
}

Анализ индексов

register(`users.${index}.email`)

Частая ошибка:

users[0].email

Для react-hook-form предпочтителен dot notation.


Проверка defaultValues

Диагностика undefined

Проблемный пример:

defaultValues: {
  user: undefined
}

Корректно:

defaultValues: {
  user: {
    email: ""
  }
}

Логирование submit cycle

Полный цикл формы

const onSub mit = (data) => {
  console.log("submit");
  console.log(data);
};

const onEr ror = (errors) => {
  console.log(errors);
};

<form onSub mit={handleSubmit(onSubmit, onError)}>

Использование mode для отладки

onChange

useForm({
  mode: "onChange"
});

Ошибки появляются сразу.


onBlur

useForm({
  mode: "onBlur"
});

Проверка после потери фокуса.


onSubmit

useForm({
  mode: "onSubmit"
});

Удобно для анализа полного submit cycle.


Strict mode и двойная валидация

В React Strict Mode функции могут вызываться дважды.

Пример:

<React.StrictMode>
  <App />
</React.StrictMode>

Это влияет на:

  • validate
  • render
  • async logic
  • resolver execution

Проверка двойных вызовов

console.count("resolver");

Кастомный debug resolver

Создание собственного resolver-обёртки:

const debugResolver = (schema) => async (data, context, options) => {
  console.log("DATA", data);

  const result = await yupResolver(schema)(
    data,
    context,
    options
  );

  console.log("RESULT", result);

  return result;
};

Использование:

useForm({
  resolver: debugResolver(schema)
});

Инспекция schema.describe

Yup позволяет получить описание схемы.

console.log(schema.describe());

Результат:

{
  type: "object",
  fields: {
    email: {
      type: "string"
    }
  }
}

Полезно для:

  • генерации UI
  • анализа структуры
  • автоматического тестирования
  • debug tooling

Проверка nullable и optional

Nullable

yup.string().nullable()

Разрешает:

null

Optional

yup.string().optional()

Разрешает:

undefined

Debugging значений

console.log(value === null);
console.log(value === undefined);

Отладка type coercion

Yup автоматически преобразует типы.

Пример:

yup.number().validate("42")

Результат:

42

strict mode

Отключение преобразований:

yup.number().strict()

Теперь:

"42"

вызовет ошибку валидации.


Инструменты браузера

Network

Полезен для анализа:

  • async validation
  • submit requests
  • debounce validation
  • API ошибок

Performance

Позволяет определить:

  • медленные validate cycle
  • тяжёлые ререндеры
  • блокировки main thread

Memory

Используется при:

  • утечках подписок
  • проблемах watch
  • некорректном unmount полей

Практика диагностики ошибок

Симптом: ошибки не появляются

Проверяются:

resolver: yupResolver(schema)
register("field")
mode
name атрибуты

Симптом: форма не отправляется

Проверяются:

handleSubmit
onSubmit
formState.errors

Симптом: лаги при вводе

Проверяются:

  • тяжёлые схемы
  • async validation
  • watch без оптимизации
  • ререндеры
  • nested object validation

Симптом: undefined значения

Проверяются:

defaultValues
Controller
reset
setValue

Полезные debug-паттерны

Inline debug panel

<pre>
  {JSON.stringify(formState.errors, null, 2)}
</pre>

Debug values

<pre>
  {JSON.stringify(watch(), null, 2)}
</pre>

Resolver timing

const resolver = async (...args) => {
  console.time("resolver");

  const result = await yupResolver(schema)(...args);

  console.timeEnd("resolver");

  return result;
};

Тестирование схем

Unit testing Yup

test("email validation", async () => {
  await expect(
    schema.validate({
      email: "wrong"
    })
  ).rejects.toThrow();
});

Проверка resolver

test("resolver errors", async () => {
  const resolver = yupResolver(schema);

  const result = await resolver(
    { email: "wrong" },
    {},
    {}
  );

  expect(result.errors.email).toBeDefined();
});