Material UI och react-hooks-form med yup-validering med TypeScript
Jag försöker använda React med Material UI och react-hook-form och yup för validering. Jag är ganska ny på alla delar av denna lista. Jag skulle vilja sätta upp ett mycket grundläggande inloggningsformulär, med den välkända Material UI-inloggningssidan och Typescript.
Jag har en mycket grundläggande validering för tillfället, endast en "obligatorisk" regel tillämpas. Min förväntning är att när jag klickar på skicka utan att fylla i formuläret, vill jag se felmeddelandet under fältet och linjen ska bli röd. Precis som i exemplen som visas på den officiella Material UI-sidan. Och här uppstår problemet... När jag klickar på skicka utan att fylla i formuläret, går fokus till det första fältet med fel och en verktygstips visas på sidan, men inte under komponenten, det är mer som en "flytande bubbla", och konturen blir inte röd. (Även felmeddelandet i den flytande bubblan är något allmänt och inte det jag satte i yup för att visas.)
Jag försökte också att ta bort yup från projektet och bara använda rules-attributet, men det ledde till samma saker.
Så här är de komponenter som jag använder. Ursäkta om det blir för långt, jag har inte ordnat något lagringsutrymme för det här projektet än:
import Button from '@material-ui/core/Button';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import { FormTextField } from '@tao-ui-shared/form';
import React from 'react';
import { FormProvider } from 'react-hook-form';
import { getLogger } from '../../logging/authenticationLogging';
import { useLoginForm } from '../hooks/useLoginForm';
import { useStyles } from './login.styles';
type LoginFormParams = {
name?: string;
classes?: Record<'submit' | 'form', string>
}
export const LoginForm: React.FC<LoginFormParams> = ({
name
}) => {
// eslint-disable-next-line @typescript-eslint/unbound-method
const { register, onSubmit, errors, ...methods } = useLoginForm();
return (
<FormProvider register={register} errors={errors} {...methods} >
<form onSubmit={onSubmit}>
<FormTextField
name='email'
id='email'
variant='outlined'
autoComplete='email'
label='Email'
required
autoFocus
errors={errors}
/>
<FormTextField
name='password'
id='password'
variant='outlined'
required
fullWidth
label='Password'
type='password'
autoComplete='current-password'
errors={errors}
/>
<Button
type="submit"
fullWidth variant="contained"
color="primary"
>
Login
</Button>
</form>
</FormProvider>
);
}
import { FormControl, TextField } from '@material-ui/core';
import React, { useEffect, useRef } from 'react';
import { Controller } from 'react-hook-form';
export type FormTextFieldParams = {
readonly name: string;
readonly id: string;
readonly variant: 'standard' | 'filled' | 'outlined' | undefined;
readonly defaultValue?: string;
readonly label?: string;
readonly margin?: 'normal' | 'none' | 'dense' | undefined;
readonly autoComplete?: string;
readonly type?: string;
readonly disabled?: boolean;
readonly required?: boolean;
readonly fullWidth?: boolean;
readonly autoFocus?: boolean;
readonly minLength?: number;
readonly maxLength?: number;
readonly errors?: any;
};
export const FormTextField: React.FC<FormTextFieldParams> = ({
id,
name,
errors,
variant = 'standard',
margin = 'normal',
label = '',
type = '',
disabled = false,
required = false,
fullWidth = true,
autoFocus = false,
autoComplete= ''
}) => {
const isError = useRef(false);
const errorMessage = useRef('');
useEffect(() => {
Object.prototype.hasOwnProperty.call(errors, name);
if (errors && Object.prototype.hasOwnProperty.call(errors, name)) {
isError.current = true;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
errorMessage.current = errors[name].message;
}
}, [errors, name]);
return (
<FormControl fullWidth={fullWidth}>
<Controller
as={TextField}
id={id}
name={name}
variant={variant}
margin={margin}
label={label}
type={type}
required={required}
fullWidth={fullWidth}
autoFocus={autoFocus}
autoComplete={autoComplete}
error={isError.current}
helperText={errorMessage.current}
disabled={disabled}
/>
</FormControl>
);
};
import { yupResolver } from '@hookform/resolvers';
import { login } from '@tao-ui-shared/authentication-service'
import { useCallback, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useDispatch } from 'react-redux';
import * as yup from 'yup';
import { LoginFormData } from '../../types';
export const useLoginForm = () => {
const validationSchema = useMemo(() => (
yup.object().shape({
email: yup.string().required('Username/email is required'),
password: yup.string().required('Password is required')
})
), []);
const methods = useForm<LoginFormData>({
resolver: yupResolver(validationSchema)
});
const dispatch = useDispatch();
const onSubmit = useCallback((formValues: LoginFormData) => {
// Do some logging here and dispatch and action...
}, []);
return {
...methods,
onSubmit: methods.handleSubmit(onSubmit)
}
}
Alla idéer om vad jag gör fel skulle uppskattas mycket!
UPPDATERING:
Jag lekte lite mer med detta. Och förmodligen, på något sätt behöver jag få mitt inneslutande formulärfältskomponent att renderas om, eftersom det inte händer när valideringen misslyckas... Kan det vara problemet?