Вместо нескольких стейтов пусть будет один - объект. Общий обработчик onChange достаёт из целевого объекта помимо значения ещё и имя инпута, одновременно являющегося именем обновляемого свойства (его придётся передавать в экземпляры CustomTextField, а внутри самого CustomTextField прокидывать внутрь TextField). Вот так (как добавить два других свойства - думаю, сами догадаетесь):
interface IFormData {
firstName: string;
lastName: string;
}
const [ formData, setFormData ] = React.useState<IFormData>({
firstName: '',
lastName: '',
});
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setFormData({
...formData,
[e.target.name]: e.target.value,
});
<CustomTextField name="firstName" value={formData.firstName} onChange={onChange} />
<CustomTextField name="lastName" value={formData.lastName} onChange={onChange} />
<!-- или -->
{Object.entries(formData).map(([ k, v ]) => (
<CustomTextField key={k} name={k} value={v} onChange={onChange} />
))}