TypeScript & React - one onChange Handler for multiple input fields
I solved this problem like this:
handleChange = (field: string) => (event) => {
this.setState({ [field]: event.target.value } as Pick<State, any>);
};
...
<Input onChange={this.handleChange('myField')} />
The Accepted answer didn't work for me.
As answered in the comments, everything you can do in JavaScript is also valid TypeScript.
However, I guess you're getting errors from the TypeScript compiler (depending on the compiler options you've set).
Assuming your component looks something like this:
interface ComponentProps { }
interface ComponentState {
name: string
address: string
}
class MyComponent extends React.Component<ComponentProps, ComponentState> {
handleChange(e) {
e.preventDefault()
this.setState({ [e.target.name]: e.target.value })
}
}
I'm getting this error:
== External: (30,19): error TS2345: Argument of type '{ [x: number]: any; }' is not assignable to parameter of type 'ComponentState'.
== External: Property 'name' is missing in type '{ [x: number]: any; }'.
And when using the noImplicitAny
compiler option (which I like to use), this additional error:
== External: (28,16): error TS7006: Parameter 'e' implicitly has an 'any' type.
If you're sure that your code is correct, you can silence these errors by explicitly casting the parameter for handleChange
and the argument for setState
handleChange(e: any) {
e.preventDefault()
this.setState({ [e.target.name]: e.target.value } as ComponentState)
}
import React, { useState } from 'react';
const Form = () => {
const [inputValues, setInputValues] = useState<{ [x: string]: string }>()
const handleFormSubmit = async (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault()
const data = {
name: inputValues?.name,
email: inputValues?.email,
phone: inputValues?.phone,
income: inputValues?.name
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
};
try {
const response = await fetch('https://xyz/form-submit', requestOptions)
const res = await response.json()
console.log(res)
} catch (error) {
console.log(error)
}
}
const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget
setInputValues(prevState => ({ ...prevState, [name]: value }))
}
return (
<div className="Form">
<div className="form-wrapper">
<h1>Demo Form for React</h1>
<form className="form">
<input
className="form-input"
name="name"
value={inputValues?.name || ''}
onChange={handleInputChange}
placeholder="Your Name"
type="text"
data-testid="form-input-name"
/>
<input
className="form-input"
name="phone"
value={inputValues?.phone || ''}
onChange={handleInputChange}
placeholder="Your Phone"
type="tel"
data-testid="form-input-phone"
/>
<input
className="form-input"
name="email"
value={inputValues?.email || ''}
onChange={handleInputChange}
placeholder="Your Email"
type="email"
data-testid="form-input-email"
/>
<input
className="form-input"
name="income"
value={inputValues?.income || ''}
onChange={handleInputChange}
placeholder="Your Annual Income"
type="number"
data-testid="form-input-income"
/>
<button
className='form-submit'
data-testid="form-submit"
onClick={handleFormSubmit}
>
Submit
</button>
</form>
</div>
</div>
);
}
export default Form;
A sample Typescript form. It's features:
- A single
onChange
handler - One state object into which we can add as many key value pairs without the typescript compiler yelling at us.
- Uses ES2020 optional chaining.
- Has data-testid on the DOM elements incase you want to run a few unit test.
- Should provide autocomplete for the input fields as per their types.
- A form Submit function sample that uses the fetch api to make a post call to an end point.
Also, with this approach, you don't have to use @ts-ignore
, any
or make changes to your tsconfig
.
Please use it as your hearts desire.