I’m pretty much new to Typescript world and am converting one of the first React applications.
Here I’m trying to set custom messages on input validation by using event.target.setCustomValidity
but I’m getting the ‘Illegal invocation’ error when the setCustomValidity function gets triggered.
Here there is the sandbox; To trigger the error it’s just required to update the first input.
Debugging the error, I’d think the issue stands on the declared type of event.target
set as HTMLInputElement
, or on the declared type of event
set as a React.SyntheticEvent
; Perhaps I’m using the wrong types for the event which doesn’t always have a setCustomValidity function?
To premise the code used on the sandbox works perfectly fine without Typescript
Any suggestion is highly appreciated, thank you in advance for your help!
Components code
App.tsx:
import React, { useState } from "react"; import Input from "./Input"; import "./styles.css"; export default function App() { // Check if value entered is a valid email. Ref: http://emailregex.com/ const emailPattern = /^(([^<>()[]\.,;:s@"]+(.[^<>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))/; const initialForm = { username: "", password: "" }; const [form, setForm] = useState(initialForm); // Listen to form inputs and updates form state respectively const handleChange = (event: React.SyntheticEvent) => { const { name, value, type, setCustomValidity } = event.target as HTMLInputElement; setForm({ ...form, [name]: value }); if (type === "email" && !emailPattern.test(value)) { setCustomValidity("Please select a valid email address."); } else { setCustomValidity(""); } }; return ( <div className="App"> <Input id="username" type="string" name="username" label="username" value={form["username"]} onChange={handleChange} required={true} /> <Input id="password" type="password" name="password" label="password" value={form["password"]} onChange={handleChange} required={true} /> </div> ); }
Input.tsx:
import React from "react"; type PropsType = { id: string; type: string; name: string; value: string; label: string; onChange: (event: React.SyntheticEvent) => void; required: boolean; }; /** * Reusable standard Input component. * @param {Object} props - Props passed from parent component containing input attributes. * @return - Controlled input element. */ const Input: React.FC<PropsType> = ({ id, type, label, onChange, required, ...rest }: PropsType) => ( <div className="input"> <input id={id} type={type} placeholder="" {...rest} onChange={(event) => onChange(event)} minLength={type === "password" ? 8 : undefined} /> <label htmlFor={id}> {label} {required && " *"} </label> </div> ); export default Input;
Advertisement
Answer
As @Heretic Monkey pointed out, you should use event.target.setCustomValidity
rather than destructuring the method to avoid losing the context of the function when it’s called. See Why are certain function calls termed “illegal invocations” in JavaScript?.
Then, to properly type the event you may want to start by changing the onChange
typing in Input.tsx
.
// src/Input.tsx type PropsType = { //... onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; };
Then update your handleChange
callback accordingly in App.tsx
.
// src/App.tsx const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { //... if (type === "email" && !emailPattern.test(value)) { event.target.setCustomValidity("Please select a valid email address."); } else { event.target.setCustomValidity(""); } }