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. ๐