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?