Создание собственной библиотеки валидации на React: дополнительные функции (часть 2)

В этой статье будет продолжена реализация библиотеки валидации, которую мы начали создавать в предыдущей части. Теперь мы реализуем дополнительный функционал.

Содержание

Показ валидации только при отправке данных

Мы проверяем все события change. Поэтому сообщения об ошибках выводятся пользователю слишком рано. Есть несколько способов исправить это.

Первое решение — использовать флаг

submitted
в качестве возвращаемого свойства хука 
useValidation
. Так мы сможем проверить, отправлены ​​ли данные, перед выводом сообщения об ошибке. Недостатком подобного подхода является запоздалое отображение кода ошибки:

<label>
  Username
  <br />
  <input {...getFieldProps('username')} />
  {submitted && errors.username && (
    <div className="error">{errors.username}</div>
  )}
</label>

Другой подход заключается в использовании второго набора ошибок (

submittedErrors
). Он пустой, если
submitted
равен false, и содержит объект
errors
, если
submitted
равен true. Мы можем реализовать это следующим образом:

const useValidation = config => {
  // как и раньше
  return {
    errors: state.errors,
    submittedErrors: state.submitted ? state.errors : {},
  };
}

Посмотрите демо-версию, демонстрирующую использование

submittedErrors
.

Отображение сообщения об ошибках при выходе из поля

Многие пользователи хотят, чтобы сообщение об ошибке выводилось, когда они покидают поле формы. Это можно реализовать, отслеживая, какие поля утратили фокус ввода. А также возвращая объект 

blurredErrors
, аналогичный описанному выше
submittedErrors
.

Реализация потребует от нас обработки нового события – 

blur
. Оно будет обновлять новый объект состояния с именем 
blurred
:

const initialState = {
  values: {},
  errors: {},
  blurred: {},
  submitted: false,
};

function validationReducer(state, action) {
  switch (action.type) {
    // как и раньше
    case 'blur':
      const blurred = { 
        ...state.blurred, 
        [action.payload]: true 
      }; 
      return { ...state, blurred };
    default:
      throw new Error('Unknown action type');
  }
}

Когда происходит действие

blur
, создается новое свойство в объекте состояния 
blurred
с именем поля в качестве ключа. Он указывает, что это поле было покинуто пользователем.

Следующий шаг — добавление свойства

onBlur
в функцию
getFieldProps
, которая генерирует это событие:

getFieldProps: fieldName => ({
  // как и раньше
  onBlur: () => {
    dispatch({ type: 'blur', payload: fieldName });
  },
}),

Также нужно задействовать 

blurredErrors
из хука
useValidation
, чтобы отображать ошибки только при необходимости.

const blurredErrors = useMemo(() => {
    const returnValue = {};
    for (let fieldName in state.errors) {
      returnValue[fieldName] = state.blurred[fieldName]
        ? state.errors[fieldName]
        : null;
    }
    return returnValue;
  }, [state.errors, state.blurred]);
return {
  // как и раньше
  blurredErrors,
};

Здесь мы создаем мемоизированную функцию, которая определяет, какие ошибки отображать на основе данных о том, было ли поле покинуто пользователем или нет. Мы пересчитываем этот набор ошибок всякий раз, когда изменяются ошибки или объекты blurred. Вы можете найти больше информации о хуке

useMemo
в документации.

Посмотреть демо

Выполняем незначительный рефакторинг

Компонент

useValidation
теперь возвращает три набора ошибок, большинство из которых в определенный момент времени будут выглядеть одинаково. Но мы позволим пользователям указывать в конфигурации, когда они хотят видеть ошибки.

Новый параметр showErrors принимает значения “submit” (по умолчанию), “always” или “blur”.

function getErrors(state, config) {
  if (config.showErrors === 'always') {
    return state.errors;
  }
  if (config.showErrors === 'blur') {
    return Object.entries(state.blurred)
      .filter(([, blurred]) => blurred)
      .reduce((acc, [name]) => ({ ...acc, [name]: state.errors[name] }), {});
  }
  return state.submitted ? state.errors : {};
}
const useValidation = config => {
  // как и раньше
  const errors = useMemo(
    () => getErrors(state, config), 
    [state, config]
  );

  return {
    errors,
    // как и раньше
  };
};

Код обработки ошибок начал занимать большую часть библиотеки, поэтому поместим его в отдельную функцию.

Если нам требуется onBlur или мгновенная проверка, нужно указать свойство

showError
в объекте конфигурации
useValidation
.

const config = {
  // как и раньше
  showErrors: 'blur',
};
const { getFormProps, getFieldProps, errors } = useValidation(config);
// ошибки теперь будут отображаться, когда поле было покинуто

Посмотреть демо

Разрешаем перекрестную валидацию

Реализуем перекрестную валидацию. То есть, валидацию одного поля, основываясь на значении другого. Для этого необходимо заставить наш пользовательский хук принимать функцию вместо объекта. Эта функция будет вызываться с текущими значениями поля:

function useValidation(config) {
  const [state, dispatch] = useReducer(...);
  if (typeof config === 'function') {
    config = config(state.values);
  }
}

Чтобы использовать это, можно передать функцию, которая возвращает объект конфигурации, 

useValidation
:

const { getFieldProps } = useValidation(fields => ({ 
  password: {
    isRequired: { message: 'Please fill out the password' },
  },
  repeatPassword: {
    isRequired: { message: 'Please fill out the password one more time' },
    isEqual: { value: fields.password, message: 'Your passwords don’t match' }
  }
}));

Здесь мы используем значение

fields.password
, чтобы убедиться, что два поля пароля содержат одинаковое значение.

Посмотрите демо, в котором реализована проверка повторного ввода пароля.

Элементы доступности

Также следует добавить правильные теги aria по умолчанию. Это поможет пользователям с ограниченными возможностями правильно понять вашу форму.

Для этого можно использовать

aria-invalid="true"
, если в поле есть ошибка. Давайте реализуем это:

const useValidation = config => {
  // как и раньше
  return {
    // как и раньше
    getFieldProps: fieldName => ({
      // как и раньше
      'aria-invalid': String(!!errors[fieldName]),
    }),
  }
};

Сокращенный синтаксис сообщений валидации

Для большинства валидаторов, присутствующих в пакете

calidators
, требуются только сообщения об ошибках. Но было бы здорово, если бы мы могли просто передать эту строку вместо объекта со свойством
message
, содержащим эту строку?

Реализуем это в функции

validateField
:

function validateField(fieldValue = '', fieldConfig, allFieldValues) {
  for (let validatorName in fieldConfig) {
    let validatorConfig = fieldConfig[validatorName];
    if (typeof validatorConfig === ’string') {
      validatorConfig = { message: validatorConfig };
    }
    const configuredValidator = validators[validatorName](validatorConfig);
    const errorMessage = configuredValidator(fieldValue);

    if (errorMessage) {
      return errorMessage;
    }
  }
  return null;
}

Теперь мы можем переписать конфигурацию валидации следующим образом:

const config = {
  username: {
    isRequired: 'The username is required',
    isEmail: 'The username should be a valid email address',
  },
};

Начальные значения полей

Иногда нужно проверить уже заполненную форму. Пользовательский хук пока не поддерживает подобную возможность. Так что давайте займемся этим!

Начальные значения будут указаны в конфигурации для каждого поля в свойстве 

initialValue
. Если начальное значение не будет отображено, по умолчанию используется пустая строка.

Создадим функцию 

getInitialState
, которая создаст начальное состояние редусера.

function getInitialState(config) {
  if (typeof config === 'function') {
    config = config({});
  }
  const initialValues = {};
  const initialBlurred = {};
  for (let fieldName in config.fields) {
    initialValues[fieldName] = config.fields[fieldName].initialValue || '';
    initialBlurred[fieldName] = false;
  }
  const initialErrors = validateFields(initialValues, config.fields);
  return {
    values: initialValues,
    errors: initialErrors,
    blurred: initialBlurred,
    submitted: false,
  };
}

Мы просматриваем все поля, проверяем, есть ли у них свойство

initialValue
. А затем устанавливаем начальное значение. После чего пропускаем эти начальные значения через валидаторы и находим начальные ошибки. Мы возвращаем объект начального состояния, который затем может быть передан хуку
useReducer
.

Так как мы вводим в конфигурацию полей свойство без валидатора. Поэтому нужно пропустить его, когда мы проверяем поля. Для этого изменим функцию

validateField
:

function validateField(fieldValue = '', fieldConfig) {
  const specialProps = ['initialValue'];
  for (let validatorName in fieldConfig) {
    if (specialProps.includes(validatorName)) {
      continue;
    }
    // as before
  }
}

Добавить другие дополнительные функции можно, используя массив

specialProps
.

Посмотреть демо

Заключение

В следующей части серии статей мы добавим функционал, который сделает нашу библиотеку валидации трендовой даже в LinkedIn.

Данная публикация представляет собой перевод статьи «Creating Your Own React Validation Library: The Features (Part 2)» , подготовленной дружной командой проекта Интернет-технологии.ру

Меню