Creación de su propia biblioteca de validación de React: las características (Parte 2)

En el artículo anterior, Kristofer explicó cómo se pueden implementar las partes básicas de una biblioteca de validación. Si bien la siguiente parte se centrará en mejorar la experiencia del desarrollador, el artículo de hoy se centrará en agregar más funciones a lo que se creó en la Parte 1 .
Implementar una biblioteca de validación no es tan difícil. Tampoco lo es agregar todas esas características adicionales que hacen que su biblioteca de validación sea mucho mejor que el resto.
Este artículo continuará implementando la biblioteca de validación que comenzamos a implementar en la parte anterior de esta serie de artículos. ¡Estas son las características que nos llevarán de una simple prueba de concepto a una biblioteca utilizable real!
- Parte 1: Conceptos básicos
- Parte 2: Las características
- Parte 3: La experiencia
Mostrar solo validación al enviar
Dado que estamos validando todos los eventos de cambio, mostramos los mensajes de error del usuario demasiado pronto para una buena experiencia de usuario. Hay algunas formas en que podemos mitigar esto.
La primera solución es simplemente proporcionar la submitted
bandera como una propiedad devuelta por el useValidation
gancho. De esta forma podremos comprobar si el formulario se envía o no antes de mostrar un mensaje de error. La desventaja aquí es que nuestro "mostrar código de error" se vuelve un poco más largo:
label Username br / input {...getFieldProps('username')} / {submitted errors.username ( div className="error"{errors.username}/div )}/label
Otro enfoque es proporcionar un segundo conjunto de errores (llamémoslos submittedErrors
), que es un objeto vacío si submitted
es falso y el errors
objeto si es verdadero. Podemos implementarlo así:
const useValidation = config = { // as before return { errors: state.errors, submittedErrors: state.submitted ? state.errors : {}, };}
De esta manera, podemos simplemente desestructurar el tipo de errores que queremos mostrar. Por supuesto, también podríamos hacer esto en el sitio de la llamada, pero al proporcionarlo aquí, lo implementaremos una vez en lugar de hacerlo dentro de todos los consumidores.
- Vea la demostración de CodeSandbox que muestra cómo
submittedErrors
se puede utilizar.
Mostrar mensajes de error en desenfoque
Mucha gente quiere que se les muestre un error una vez que abandonan un determinado campo. Podemos agregar soporte para esto, rastreando qué campos se han "difuminado" (alejándose de ellos) y devolviendo un objeto blurredErrors
, similar al submittedErrors
anterior.
La implementación requiere que manejemos un nuevo tipo de acción blur
, que actualizará un nuevo objeto de estado llamado blurred
:
const initialState = { values: {}, errors: {}, blurred: {}, submitted: false,};function validationReducer(state, action) { switch (action.type) { // as before case 'blur': const blurred = { ...state.blurred, [action.payload]: true }; return { ...state, blurred }; default: throw new Error('Unknown action type'); }}
Cuando enviamos la blur
acción, creamos una nueva propiedad en el blurred
objeto de estado con el nombre del campo como clave, lo que indica que ese campo se ha desdibujado.
El siguiente paso es agregar un onBlur
accesorio a nuestra getFieldProps
función, que envíe esta acción cuando corresponda:
getFieldProps: fieldName = ({ // as before onBlur: () = { dispatch({ type: 'blur', payload: fieldName }); },}),
Finalmente, debemos proporcionar blurredErrors
desde nuestro useValidation
enlace para que podamos mostrar los errores solo cuando sea necesario.
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 { // as before blurredErrors,};
Aquí, creamos una función memorizada que determina qué errores mostrar en función de si el campo se ha difuminado o no. Recalculamos este conjunto de errores cada vez que cambian los errores u objetos borrosos. Puedes leer más sobre el useMemo
gancho en la documentación .
- Ver demostración de CodeSandbox
Es hora de una pequeña refactorización
Nuestro useValidation
componente ahora devuelve tres conjuntos de errores, la mayoría de los cuales tendrán el mismo aspecto en algún momento. En lugar de seguir esta ruta, permitiremos que el usuario especifique en la configuración cuándo quiere que aparezcan los errores en su formulario.
Nuestra nueva opción showErrors
aceptará "enviar" (el valor predeterminado), "siempre" o "desenfocar". Podemos agregar más opciones más adelante, si es necesario.
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 = { // as before const errors = useMemo( () = getErrors(state, config), [state, config] ); return { errors, // as before };};
Dado que el código de manejo de errores comenzó a ocupar la mayor parte de nuestro espacio, lo estamos refactorizando para que tenga su propia función. Si no sigues las instrucciones Object.entries
y .reduce
demás, está bien, es una reescritura del for...in
código en la última sección.
Si requiriéramos onBlur o validación instantánea, podríamos especificar el showError
accesorio en nuestro useValidation
objeto de configuración.
const config = { // as before showErrors: 'blur',};const { getFormProps, getFieldProps, errors } = useValidation(config);// errors would now only include the ones that have been blurred
- Ver demostración de CodeSandbox
Nota sobre supuestos
“Tenga en cuenta que ahora estoy asumiendo que cada formulario querrá mostrar los errores de la misma manera (siempre al enviar, siempre borrosos, etc.). Esto podría ser cierto para la mayoría de las aplicaciones, pero probablemente no para todas. Ser consciente de sus suposiciones es una parte muy importante de la creación de su API ".
Permitir validación cruzada
Una característica realmente poderosa de una biblioteca de validación es permitir la validación cruzada, es decir, basar la validación de un campo en el valor de otro campo.
Para permitir esto, necesitamos hacer que nuestro gancho personalizado acepte una función en lugar de un objeto. Esta función se llamará con los valores de campo actuales. ¡Implementarlo en realidad son solo tres líneas de código!
function useValidation(config) { const [state, dispatch] = useReducer(...); if (typeof config === 'function') { config = config(state.values); }}
Para usar esta característica, simplemente podemos pasar una función que devuelva el objeto de configuración a 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' } }}));
Aquí, usamos el valor de fields.password
para asegurarnos de que dos campos de contraseña contengan la misma entrada (lo cual es una experiencia de usuario terrible, pero eso es para otra publicación de blog).
- Vea la demostración de CodeSandbox que no permite que el nombre de usuario y la contraseña tengan el mismo valor.
Agregue algunas ventajas de accesibilidad
Una buena cosa que puedes hacer cuando estás a cargo de los accesorios de un campo es agregar las etiquetas aria correctas de forma predeterminada. Esto ayudará a los lectores de pantalla a explicar su formulario.
Una mejora muy sencilla es agregar aria-invalid="true"
si el campo tiene algún error. Implementemos eso:
const useValidation = config = { // as before return { // as before getFieldProps: fieldName = ({ // as before 'aria-invalid': String(!!errors[fieldName]), }), }};
Esa es una línea de código adicional y una experiencia de usuario mucho mejor para los usuarios de lectores de pantalla.
Quizás te preguntes por qué escribimos String(!!state.errors[fieldName])
. state.errors[fieldName]
es una cadena, y el operador de doble negación nos da un valor booleano (y no sólo un valor verdadero o falso). Sin embargo, la aria-invalid
propiedad debe ser una cadena (también puede leerse “gramática” u “ortografía”, además de “verdadero” o “falso”), por lo que debemos convertir ese booleano en su equivalente de cadena.
Todavía quedan algunos ajustes más que podríamos hacer para mejorar la accesibilidad, pero parece un buen comienzo.
Sintaxis abreviada del mensaje de validación
La mayoría de los validadores del calidators
paquete (y supongo que la mayoría de los demás validadores) solo requieren un mensaje de error. ¿No sería bueno si pudiéramos pasar esa cadena en lugar de un objeto con una message
propiedad que contenga esa cadena?
Implementemos eso en nuestra validateField
función:
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;}
De esta manera, podemos reescribir nuestra configuración de validación así:
const config = { username: { isRequired: 'The username is required', isEmail: 'The username should be a valid email address', },};
¡Mucho más limpio!
Valores de campo iniciales
A veces necesitamos validar un formulario que ya está completo. Nuestro gancho personalizado aún no admite eso, ¡así que vamos a ello!
Los valores de campo iniciales se especificarán en la configuración de cada campo, en la propiedad initialValue
. Si no se especifica, el valor predeterminado es una cadena vacía.
Vamos a crear una función getInitialState
, que creará el estado inicial de nuestro reductor por nosotros.
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, };}
Revisamos todos los campos, comprobamos si tienen una initialValue
propiedad y establecemos el valor inicial en consecuencia. Luego pasamos esos valores iniciales a través de los validadores y también calculamos los errores iniciales. Devolvemos el objeto de estado inicial, que luego se puede pasar a nuestro useReducer
gancho.
Dado que estamos introduciendo un accesorio no validador en la configuración de campos, debemos omitirlo cuando validemos nuestros campos. Para hacer eso, cambiamos nuestra validateField
función:
function validateField(fieldValue = '', fieldConfig) { const specialProps = ['initialValue']; for (let validatorName in fieldConfig) { if (specialProps.includes(validatorName)) { continue; } // as before }}
A medida que sigamos agregando más funciones como esta, podemos agregarlas a nuestra specialProps
matriz.
- Ver demostración de CodeSandbox
Resumiendo
Estamos en camino de crear una increíble biblioteca de validación. Hemos agregado toneladas de funciones y ya somos líderes de opinión.
En la siguiente parte de esta serie, agregaremos todos esos extras que hacen que nuestra biblioteca de validación sea incluso tendencia en LinkedIn.
(dm, yk, il)Explora más en
- Reaccionar
- javascript
- Pruebas
- Formularios
Deja un comentario