Here is my Login.tsx component which uses a child component input.tsx to handle all the inputs related tasks.
import React, { useState } from "react";
import Input from "../../components/Form/Input/Input";
import Button from "../../components/Button/Button";
import { required, length, email } from "../../util/validators";
import Auth from "./Auth";
// Types for form fields
interface FormField {
value: string;
valid: boolean;
touched: boolean;
validators: Array<(value: string) => boolean>;
}
interface LoginForm {
email: FormField;
password: FormField;
formIsValid: boolean;
}
interface LoginProps {
onLogin: (
event: React.FormEvent,
credentials: { email: string; password: string }
) => void;
loading: boolean;
}
const Login: React.FC<LoginProps> = ({ onLogin, loading }) => {
const [loginForm, setLoginForm] = useState<LoginForm>({
email: {
value: "",
valid: false,
touched: false,
validators: [required, email],
},
password: {
value: "",
valid: false,
touched: false,
validators: [required, length({ min: 5 })],
},
formIsValid: false,
});
// Handler for input changes
const inputChangeHandler = (input: keyof LoginForm, value: string) => {
setLoginForm((prevState: LoginForm) => {
const updatedField = prevState[input] as FormField;
let isValid = true;
// Check all validators for this input
for (const validator of updatedField.validators) {
isValid = isValid && validator(value);
}
const updatedForm = {
...prevState,
[input]: {
...updatedField,
valid: isValid,
value: value,
},
};
// Check if the entire form is valid
let formIsValid = true;
for (const inputName in updatedForm) {
if (inputName !== 'formIsValid') {
const field = updatedForm[inputName as keyof LoginForm] as FormField;
if ('valid' in field) {
formIsValid =
formIsValid && field.valid;
}
}
}
return {
...updatedForm,
formIsValid: formIsValid,
};
});
};
// Handler for input blur event (touched field)
const inputBlurHandler = (input: keyof LoginForm) => {
setLoginForm((prevState) => ({
...prevState,
[input]: {
...prevState[input] as FormField,
touched: true,
},
}));
};
return (
<Auth>
<form
onSubmit={(e) =>
onLogin(e, {
email: loginForm.email.value,
password: loginForm.password.value,
})
}
>
<Input
id="email"
label="Your E-Mail"
type="email"
control="input"
onChange={(event: any) =>
inputChangeHandler("email", event.target.value)
}
onBlur={() => inputBlurHandler("email")}
value={loginForm.email.value}
valid={loginForm.email.valid}
touched={loginForm.email.touched}
/>
<Input
id="password"
label="Password"
type="password"
control="input"
onChange={(event: any) =>
inputChangeHandler("password", event.target.value)
}
onBlur={() => inputBlurHandler("password")}
value={loginForm.password.value}
valid={loginForm.password.valid}
touched={loginForm.password.touched}
/>
<Button design="raised" type="submit" loading={loading}>
Login
</Button>
</form>
</Auth>
);
};
export default Login;
import './Input.css';
const input = (props: any) => (
<div className="input">
{props.label && <label htmlFor={props.id}>{props.label}</label>}
{props.control === 'input' && (
<input
className={[
!props.valid ? 'invalid' : 'valid',
props.touched ? 'touched' : 'untouched'
].join(' ')}
type={props.type}
id={props.id}
required={props.required}
value={props.value}
placeholder={props.placeholder}
onChange={e => props.onChange(props.id, e.target.value, e.target.files)}
onBlur={props.onBlur}
/>
)}
{props.control === 'textarea' && (
<textarea
className={[
!props.valid ? 'invalid' : 'valid',
props.touched ? 'touched' : 'untouched'
].join(' ')}
id={props.id}
rows={props.rows}
required={props.required}
value={props.value}
onChange={e => props.onChange(props.id, e.target.value)}
onBlur={props.onBlur}
/>
)}
</div>
);
export default input;
Unfortunately, I am unable to type any input into my input fields on the login page. Also, I have see this error message in the dev console.
Login.tsx:113 Uncaught TypeError: Cannot read properties of undefined (reading 'value') at Object.onChange (Login.tsx:113:54) at onChange (Input.tsx:21:30)
I have tried to manually test the components with console log to check whether my key presses are read or not. It does logs the keys in the console but nothing is showing on the input fields.
Look at what you're sending to your onChange
function:
props.onChange(props.id, e.target.value)
And look at how you're using it:
onChange={(event: any) =>
inputChangeHandler("password", event.target.value)
}
Presumably props.id
is a simple value, such as a string or a number. Which has no target
property. Which means you can't read a property called .value
from .target
, exactly as the error is indicating.
Perhaps you meant to expect the value instead:
onChange={(val: any) =>
inputChangeHandler("password", val)
}
Or perhaps you meant to pass the event:
props.onChange(e)
Or perhaps something else entirely, as you seem to be mixing up the input's value with an id
of some kind. But in any event, now is the time for you to take a look at what you're passing back in those change
events and what you want to do with that information.