How I think when I write a React component. 🤔

potouridisio

Ioannis Potouridis

Posted on November 2, 2019

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?

💖 💪 🙅 🚩
potouridisio
Ioannis Potouridis

Posted on November 2, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related