This is going to be a short demonstration on how I usually think when I write a React component.
So, let's say that I want to create a form component.
I don't care what fields the form will have at the moment.
import React from 'react'; function Form() { return ( <form> {/* */} </form> ) } export default Form;
I want to add a firstName
field.
import React, { useState } from 'react'; function Form() { const [firstName, setFirstName] = useState(''); const handleFirstNameChange = ({ target }) => { setFirstName(target.value); } return ( <form> <div> <label htmlFor="firstName">First name</label> <div> <input id="firstName" onChange={handleFirstNameChange} type="text" value={firstName} /> </div> </div> </form> ) } export default Form;
Looking good. π
I want to add a lastName
field.
import React, { useState } from 'react'; function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const handleFirstNameChange = ({ target }) => { setFirstName(target.value); } const handleLastNameChange = ({ target }) => { setLastName(target.value); } return ( <form> <div> <label htmlFor="firstName">First name</label> <div> <input id="firstName" onChange={handleFirstNameChange} type="text" value={firstName} /> </div> </div> <div> <label htmlFor="lastName">Last name</label> <div> <input id="lastName" onChange={handleLastNameChange} type="text" value={lastName} /> </div> </div> </form> ) } export default Form;
Adding that second field was way easier.
I used my copy paste
powers.
I want to add an email
field.
I shall use my powers once again. π±βπ
import React, { useState } from 'react'; function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState(''); const handleFirstNameChange = ({ target }) => { setFirstName(target.value); } const handleLastNameChange = ({ target }) => { setLastName(target.value); } const handleEmailChange = ({ target }) => { setEmail(target.value); } return ( <form> <div> <label htmlFor="firstName">First name</label> <div> <input id="firstName" onChange={handleFirstNameChange} type="text" value={firstName} /> </div> </div> <div> <label htmlFor="lastName">Last name</label> <div> <input id="lastName" onChange={handleLastNameChange} type="text" value={lastName} /> </div> </div> <div> <label htmlFor="email">Email</label> <div> <input id="email" onChange={handleEmailChange} type="email" value={email} /> </div> </div> </form> ) } export default Form;
...
Then I want to add a password
field.
...
Then I want to add another field.
...
...
STOP! π€
Every new field translates into these three changes:
- Adding a state and set state action for the field
- Adding a new event handler for the input
- Adding the HTML for the field
It's time for me now to use my real powers.
I'll attempt to decrease the number of changes that occur.
I don't want to add a new event handler for every input.
The only thing that changes in every event handler is the action that gets called.
I'll pass that as an argument.
import React, { useState } from 'react'; function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState(''); const handleChange = setStateAction => ({ target }) => { setStateAction(target.value); } return ( <form> <div> <label htmlFor="firstName">First name</label> <div> <input id="firstName" onChange={handleChange(setFirstName)} type="text" value={firstName} /> </div> </div> <div> <label htmlFor="lastName">Last name</label> <div> <input id="lastName" onChange={handleChange(setLastName)} type="text" value={lastName} /> </div> </div> <div> <label htmlFor="email">Email</label> <div> <input id="email" onChange={handleChange(setEmail)} type="email" value={email} /> </div> </div> </form> ) } export default Form;
I'll try to add that password
field now.
import React, { useState } from 'react'; function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleChange = setStateAction => ({ target }) => { setStateAction(target.value); } return ( <form> <div> <label htmlFor="firstName">First name</label> <div> <input id="firstName" onChange={handleChange(setFirstName)} type="text" value={firstName} /> </div> </div> <div> <label htmlFor="lastName">Last name</label> <div> <input id="lastName" onChange={handleChange(setLastName)} type="text" value={lastName} /> </div> </div> <div> <label htmlFor="email">Email</label> <div> <input id="email" onChange={handleChange(setEmail)} type="email" value={email} /> </div> </div> <div> <label htmlFor="password">Password</label> <div> <input id="password" onChange={handleChange(setPassword)} type="password" value={password} /> </div> </div> </form> ) } export default Form;
OK, looking a bit better.
I think I can cross that out from the list.
- Adding a state and set state action for the field
Adding a new event handler for the input- Adding the HTML for the field
I don't want to add a new state and set state action for every field.
I'll update the event handler since I'll use one set state action.
I'll also add a name property to those inputs.
import React, { useState } from 'react'; function Form() { const [values, setValues] = useState({}); const handleChange = ({ target }) => { setValues(prev => ({ ...prev, [target.name]: target.value })); } return ( <form> <div> <label htmlFor="firstName">First name</label> <div> <input id="firstName" name="firstName" onChange={handleChange} type="text" value={values.firstName || ''} /> </div> </div> <div> <label htmlFor="lastName">Last name</label> <div> <input id="lastName" name="lastName" onChange={handleChange} type="text" value={values.lastName || ''} /> </div> </div> <div> <label htmlFor="email">Email</label> <div> <input id="email" name="email" onChange={handleChange} type="email" value={values.email || ''} /> </div> </div> <div> <label htmlFor="password">Password</label> <div> <input id="password" name="password" onChange={handleChange} type="password" value={values.password || ''} /> </div> </div> </form> ) } export default Form;
OK, I'll cross that one out as well.
Adding a state and set state action for the fieldAdding a new event handler for the input- Adding the HTML for the field
This is me going berserk now.
import React, { useState } from 'react'; const fields = [ { id: 'firstName', label: 'First name', name: 'firstName', type: 'text' }, { id: 'lastName', label: 'Last name', name: 'lastName', type: 'text' }, { id: 'email', label: 'Email', name: 'email', type: 'email' }, { id: 'password', label: 'Password', name: 'password', type: 'password' } ]; function Form() { const [values, setValues] = useState({}); const handleChange = ({ target }) => { setValues(prev => ({ ...prev, [target.name]: target.value })); } return ( <form> {fields.map(({ id, label, name, type }, index) => ( <div key={index}> <label htmlFor={id}>{label}</label> <div> <input id={id} name={name} onChange={handleChange} type={type} value={values[name] || ''} /> </div> </div> ))} </form> ) } export default Form;
Well, now when I want to add a field, I just add one in my fields array. π
Adding a state and set state action for the fieldAdding a new event handler for the inputAdding the HTML for the field
What do you think?
Top comments (12)
Personally, I think it's better for the input field to be separated into a new component?
Definitely!
I love the concept. But don't you think it makes the code less readable.
Well, I agree! But it also makes it more reusable.
You can go either way.
IMO it's always a balancing act.
Nice. One observation only, if you use desconctructive for the map function object, why don't you use it on the handleChange also?
No reason at all, I got used to write that handler this way.
I'll update it for code consistency though. Thanks! π
You're missing the input type π
Ah! Thanks! π
I like it. It's just a generalization about making code more concise in React. Very readable after you've been writing code like this for a while
I agree.
I believe readable code means what one understands.
Whenever something seemed unreadable to me I always asked myself -Why is this written this way and how does it work?
Nice example!
Just one thing, using index as a key is not a good practice, field is also unique, so better use it as a key (+one less variable)
I agree, good point!
Since the example uses fields that are presented as user input with no unique identifiers though, I felt safer using the index. π