I was working on a react hooks project.
I was making a form
In the form, I noticed myself writing a very wet code
Here is my code
const FormInvite = (props) => {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [country, setCountry] = useState("")
const [email, setEmai] = useState("")
const changeSelectedCountry = (event) => {
setCountry(event.target.value)
}
const changeFirstName = (event) => {
setFirstName(event.target.value)
}
const changeLastName = (event) => {
setLastName(event.target.value)
}
const changeEmail = (event) => {
setEmail(event.target.value)
}
Can someone help me/give recommendation so that I don't write those one liner, kind of representative code?
1 Answer 1
Given your form fields use appropriate names (i.e. field names match state properties), and you don't mind using a single state object you can create a single change handler that merges in each field update (similar to how a class-based component's this.setState
would handle it) using the field name as the key.
const FormInvite = (props) => {
const [state, setState] = useState({
firstName: '',
lastName: '',
country: '',
email: '',
});
const changeHandler = e => {
const { name, value} = e.target;
setState(state => ({ ...state, [name]: value }));
}
...
const rootElement = document.getElementById("root");
const FormInvite = props => {
const [state, setState] = React.useState({
firstName: "",
lastName: "",
country: "",
email: ""
});
const changeHandler = e => {
const { name, value } = e.target;
setState(state => ({ ...state, [name]: value }));
};
React.useEffect(() => {
console.log('firstname changed', state.firstName);
}, [state.firstName]);
React.useEffect(() => {
console.log('lastname changed', state.lastName);
}, [state.lastName]);
React.useEffect(() => {
console.log('country changed', state.country);
}, [state.country]);
React.useEffect(() => {
console.log('email changed', state.email);
}, [state.email]);
return (
<form>
<label>
First
<input
type="text"
value={state.firstName}
name="firstName"
onChange={changeHandler}
/>
</label>
<label>
Last
<input
type="text"
value={state.lastName}
name="lastName"
onChange={changeHandler}
/>
</label>
<label>
Country
<input
type="text"
value={state.country}
name="country"
onChange={changeHandler}
/>
</label>
<label>
email
<input
type="email"
value={state.email}
name="email"
onChange={changeHandler}
/>
</label>
</form>
);
};
ReactDOM.render(
<React.StrictMode>
<FormInvite />
</React.StrictMode>,
rootElement
);
input {
width: 3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<body>
<div id="root"></div>
</body>
NOTE: This uses a functional state update to ensure/preserve previous state values that aren't currently being updated.
Alternatively you can create a mapping of state => setState function and use a similar change handler as before, this time though you won't need to merge in updates manually.
const FormInvite = props => {
const [firstName, setFirstName] = React.useState("");
const [lastName, setLastName] = React.useState("");
const [country, setCountry] = React.useState("");
const [email, setEmail] = React.useState("");
const fieldSetStateMap = {
firstName: setFirstName,
lastName: setLastName,
country: setCountry,
email: setEmail
};
const changeHandler = e => {
const { name, value } = e.target;
fieldSetStateMap[name](value);
};
...
const rootElement = document.getElementById("root");
const FormInvite = props => {
const [firstName, setFirstName] = React.useState("");
const [lastName, setLastName] = React.useState("");
const [country, setCountry] = React.useState("");
const [email, setEmail] = React.useState("");
const fieldSetStateMap = {
firstName: setFirstName,
lastName: setLastName,
country: setCountry,
email: setEmail
};
const changeHandler = e => {
const { name, value } = e.target;
fieldSetStateMap[name](value);
};
React.useEffect(() => {
console.log("firstname changed", firstName);
}, [firstName]);
React.useEffect(() => {
console.log("lastname changed", lastName);
}, [lastName]);
React.useEffect(() => {
console.log("country changed", country);
}, [country]);
React.useEffect(() => {
console.log("email changed", email);
}, [email]);
return (
<form>
<label>
First
<input
type="text"
value={firstName}
name="firstName"
onChange={changeHandler}
/>
</label>
<label>
Last
<input
type="text"
value={lastName}
name="lastName"
onChange={changeHandler}
/>
</label>
<label>
Country
<input
type="text"
value={country}
name="country"
onChange={changeHandler}
/>
</label>
<label>
email
<input
type="email"
value={email}
name="email"
onChange={changeHandler}
/>
</label>
</form>
);
};
ReactDOM.render(
<React.StrictMode>
<FormInvite />
</React.StrictMode>,
rootElement
);
input {
width: 3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<body>
<div id="root"></div>
</body>
NOTE: This can use a plain state update as each "piece of state" is fully independent of other state and the value is replaced each onChange
.