DEV Community

Ioannis Potouridis
Ioannis Potouridis

Posted on • Updated on

How I think when I write a React component. 🤔

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:

  1. Adding a state and set state action for the field
  2. Adding a new event handler for the input
  3. 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.

  1. Adding a state and set state action for the field
  2. Adding a new event handler for the input
  3. 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.

  1. Adding a state and set state action for the field
  2. Adding a new event handler for the input
  3. 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. 😁

  1. Adding a state and set state action for the field
  2. Adding a new event handler for the input
  3. Adding the HTML for the field

What do you think?

Top comments (12)

Collapse
 
loq24 profile image
Lougie

Personally, I think it's better for the input field to be separated into a new component?

Collapse
 
potouridisio profile image
Ioannis Potouridis

Definitely!

Collapse
 
monfernape profile image
Usman Khalil

I love the concept. But don't you think it makes the code less readable.

Collapse
 
potouridisio profile image
Ioannis Potouridis

Well, I agree! But it also makes it more reusable.

You can go either way.

IMO it's always a balancing act.

Collapse
 
sabbin profile image
Sabin Pandelovitch • Edited

Nice. One observation only, if you use desconctructive for the map function object, why don't you use it on the handleChange also?

Collapse
 
potouridisio profile image
Ioannis Potouridis

No reason at all, I got used to write that handler this way.

I'll update it for code consistency though. Thanks! 👍

Collapse
 
dinsmoredesign profile image
Derek D

You're missing the input type 😞

Collapse
 
potouridisio profile image
Ioannis Potouridis

Ah! Thanks! 👍

Collapse
 
seanmclem profile image
Seanmclem

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

Collapse
 
potouridisio profile image
Ioannis Potouridis

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?

Collapse
 
spacer33 profile image
spacer33 • Edited

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)

Collapse
 
potouridisio profile image
Ioannis Potouridis

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