form.ts
import React from 'react';
type UnifiedChangeEvent =
| React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
| React.ChangeEvent<{ name?: string; value: unknown }>
| (Event & { target: { name: string; value: unknown } });
export type ErrorObject<T> = Partial<Record<keyof T, string | boolean>>;
function handleInputChange<T>(
values: T,
setValues: (o: T) => void,
fieldName: keyof T
) {
return {
onChange(event: UnifiedChangeEvent) {
const { name, value } = event.target;
setValues({ ...values, [name!]: value });
},
name: fieldName,
value: values[fieldName],
};
}
export function setupInputChange<T>(values: T, setValues: (o: T) => void) {
return (fieldName: keyof T) =>
handleInputChange(values, setValues, fieldName);
}
function handleErrorChange<T>(errors: T, fieldName: keyof T) {
return errors[fieldName] && { error: true, helperText: errors[fieldName] };
}
export function setupErrorChange<T>(errors: T) {
return (fieldName: keyof T) => handleErrorChange(errors, fieldName);
}
export function isValid<T>(errors: ErrorObject<T>): boolean {
for (const v of Object.values(errors)) {
if (v) {
return false;
}
}
return true;
}
export function hasErrors<T>(errors: ErrorObject<T>) {
return !isValid(errors);
}
export function setupInputProps<T, U>(
values: T,
setValues: (o: T) => void,
errors: ErrorObject<U>,
setErrors: (o: ErrorObject<U>) => void
) {
return {
handleInputProps: (fieldName: keyof T & keyof U) => ({
...handleInputChange(values, setValues, fieldName),
...handleErrorChange(errors, fieldName),
}),
checkValidators: (errors: ErrorObject<U>) => {
setErrors(errors);
return isValid(errors);
},
};
}
Example use:
import { FormEvent, useState } from 'react';
import {
Button,
TextField,
} from '@mui/material';
import './App.css';
import { setupInputProps, type ErrorObject } from './utils/form';
class DonationCandidateDto {
donationCandidateId = 0;
fullname = '';
mobile = '';
email = '';
age = 0;
bloodGroup = '';
address = '';
active = false;
}
const initialFieldValues: DonationCandidateDto = {
donationCandidateId: 0,
fullname: '',
mobile: '',
email: '',
age: 0,
bloodGroup: '',
address: '',
active: false,
};
type FormType = typeof initialFieldValues;
// out-of-band validations
type OtherStates = {
areZombiesInLab: boolean;
day?: number;
};
function App() {
const [values, setValues] = useState(structuredClone(initialFieldValues));
const [errors, setErrors] = useState<ErrorObject<FormType & OtherStates>>({});
const { handleInputProps, checkValidators } = setupInputProps(
values,
setValues,
errors,
setErrors
);
return (
<div>
<h1>Hello world</h1>
<form autoComplete="off" noValidate onSubmit={handleSubmit}>
<div>
<TextField
label="Full name"
required={true}
{...handleInputProps('fullname')}
/>
</div>
<br />
<div>
<TextField label="Age" {...handleInputProps('age')} />
</div>
<div>
{errors.areZombiesInLab && (
<div style={{ color: 'red' }}>{errors.areZombiesInLab}</div>
)}
</div>
<br />
<div>
<Button color="primary" type="submit" variant="contained">
Submit
</Button>
<Button type="submit" color="inherit" variant="contained">
Reset
</Button>
</div>
</form>
<hr />
{/* {JSON.stringify(values)} */}
</div>
);
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const isValid = checkValidators({
fullname: !values.fullname && 'Must have full name',
age: !(values.age >= 18) && 'Must be an adult',
// areZombiesInLab: true && 'Zombies in lab',
});
if (!isValid) {
return;
}
// POST/PUT here
}
}
export default App;
If the name is not existing from the DTO, it will not compile:
No comments:
Post a Comment