Better way of handling form data via React useState()?

Viewed 1397

Just wondering if anyone has a better solution to stop repetitiveness in my code? I currently am using useState() to handle user data and it's fairly repetitive as there are loads of different fields. Here is my code below:

const [lname, setLast] = useState<string>("");
const [country, setCountry] = useState<string>("");
const [phone, setPhone] = useState<string>("");
const [email, setUsername] = useState<string>("");
const [username, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [cpassword, setCPassword] = useState<string>("");

The Form itself (summarized):

<IonItem>
    <IonLabel position={"stacked"}>First Name</IonLabel>
    <IonInput type={"text"} value={fname} placeholder="Enter Your First Name" onIonChange={(event) => setFirst(event.detail.value ?? '')}/>
</IonItem>

I saw before in another post you could use an interface, but I am unsure how I could implement it with the useState() or if it would be the best approach for my code as I am then passing the data to an Axios POST request to the backend to be processed

    const handleRegistration = async () => {
        await axios.post('http://localhost:3000/register', {
            fname: fname,
            lname: lname,
            country: country,
            phone: phone,
            email: email,
            username: username,
            password: password,
            cpassword: cpassword
        }).then(res => {
            console.log(res);
            onRegister();
        }).catch(err => {
            console.log(err);
        });
    }
3 Answers

You can handle control data in a single useState :

import React, { useState } from "react";

const initialValues = {
        fname: "",
        lname: "",
        country: "",
        phone: "",
        email: "",
        username: "",
        password: "",
        cpassword: ""
};

export default function Form() {
  const [values, setValues] = useState(initialValues);

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setValues({
      ...values,
      [name]: value,
    });
  };

  return (
        <form>
          <input
            value={values.fname}
            onChange={handleInputChange}
            name="fname"
            label="fname"
          />
          <input
            value={values.lname}
            onChange={handleInputChange}
            name="lname"
            label="lname"
          />
           // ... Rest of the input fields
          <button type="submit"> Submit </button>
        </form>
  );
}

Also, when sending the payload:

const handleRegistration = async () => {
        await axios.post('http://localhost:3000/register', {
            fname: values.fname,
            lname: values.lname,
            //Rest of the input fields
        }).then(res => {
            console.log(res);
            onRegister();
        }).catch(err => {
            console.log(err);
        });
    }

Instead of using individual values for state fields, you can use an object and provide a interface

interface FormState = {
  lname: string;
  country: string;
  phone: string;
  email: string;
  username: string;
  password: string;
  cpassword: string;
};
const [values, setValues] = useState<FormState>({
  lname: "",
  country: "",
  phone: "",
  email: "",
  username: "",
  password: "",
  cpassword: "",
});

const handleInputChange = (field) => (e) => {
    const val = e.detail.value;
    setValues(values => ({
      ...values,
      [field]: val,
    }));
  };


<IonItem>
    <IonLabel position={"stacked"}>First Name</IonLabel>
    <IonInput type={"text"} value={fname} placeholder="Enter Your First Name" onIonChange={handleInputChange('fname')}/>
</IonItem>
import React, { useState } from 'react';

const Form = () => {
  const [inputValues, setInputValues] = useState<{ [x: string]: string }>()

  const handleFormSubmit = async (e: React.MouseEvent<HTMLElement>) => {
    e.preventDefault()
    const data = {
      name: inputValues?.name,
      email: inputValues?.email,
      phone: inputValues?.phone,
      income: inputValues?.name
    }

    const requestOptions = {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    };

    try {
      const response = await fetch('https://xyz/form-submit', requestOptions)
      const res = await response.json()
      console.log(res)
    } catch (error) {
      console.log(error)
    }
  }

  const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => {
    const { name, value } = e.currentTarget
    setInputValues(prevState => ({ ...prevState, [name]: value }))
  }

  return (
    <div className="Form">
      <div className="form-wrapper">
        <h1>Demo Form for React</h1>
        <form className="form">
          <input 
            className="form-input"
            name="name" 
            value={inputValues?.name || ''} 
            onChange={handleInputChange} 
            placeholder="Your Name"
            type="text"
            data-testid="form-input-name"
          />
          <input 
            className="form-input"
            name="phone" 
            value={inputValues?.phone || ''} 
            onChange={handleInputChange} 
            placeholder="Your Phone" 
            type="tel"
            data-testid="form-input-phone"
          />
          <input 
            className="form-input" 
            name="email"
            value={inputValues?.email || ''} 
            onChange={handleInputChange} 
            placeholder="Your Email" 
            type="email"
            data-testid="form-input-email"
          />
          <input 
            className="form-input"
            name="income"
            value={inputValues?.income || ''} 
            onChange={handleInputChange} 
            placeholder="Your Annual Income" 
            type="number"
            data-testid="form-input-income"
          />
          <button 
            className='form-submit'
            data-testid="form-submit" 
            onClick={handleFormSubmit}
          >
            Submit
          </button>
        </form>
      </div>
    </div>
  );
}

export default Form;

A sample Typescript form. It's features:

  • A single onChange handler
  • One state object into which we can add as many key value pairs without the typescript compiler yelling at us.
  • Uses ES2020 optional chaining.
  • Has data-testid on the DOM elements incase you want to run a few unit test.
  • Should provide autocomplete for the input fields as per their types.
  • A form Submit function sample that uses the fetch api to make a post call to an end point.

Please use it as your heart's desire.

p.s: For the most part its typesafe. But, you can always use generics to add intellisense for the state object.

Related