4
\$\begingroup\$

I was working on a react hooks project.

I was making a form

In the form, I noticed myself writing a very wet code

Here is my code

const FormInvite = (props) => {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [country, setCountry] = useState("")
  const [email, setEmai] = useState("")

  const changeSelectedCountry = (event) => {
    setCountry(event.target.value)
  }

  const changeFirstName = (event) => {
    setFirstName(event.target.value)
  }

  const changeLastName = (event) => {
    setLastName(event.target.value)
  }

  const changeEmail = (event) => {
    setEmail(event.target.value)
  }

Can someone help me/give recommendation so that I don't write those one liner, kind of representative code?

\$\endgroup\$
2
\$\begingroup\$

Given your form fields use appropriate names (i.e. field names match state properties), and you don't mind using a single state object you can create a single change handler that merges in each field update (similar to how a class-based component's this.setState would handle it) using the field name as the key.

const FormInvite = (props) => {
  const [state, setState] = useState({
    firstName: '',
    lastName: '',
    country: '',
    email: '',
  });

  const changeHandler = e => {
    const { name, value} = e.target;
    setState(state => ({ ...state, [name]: value }));
  }
  ...

const rootElement = document.getElementById("root");

const FormInvite = props => {
  const [state, setState] = React.useState({
    firstName: "",
    lastName: "",
    country: "",
    email: ""
  });

  const changeHandler = e => {
    const { name, value } = e.target;
    setState(state => ({ ...state, [name]: value }));
  };

  React.useEffect(() => {
    console.log('firstname changed', state.firstName);
  }, [state.firstName]);
  React.useEffect(() => {
    console.log('lastname changed', state.lastName);
  }, [state.lastName]);
  React.useEffect(() => {
    console.log('country changed', state.country);
  }, [state.country]);
  React.useEffect(() => {
    console.log('email changed', state.email);
  }, [state.email]);

  return (
    <form>
      <label>
        First
        <input
          type="text"
          value={state.firstName}
          name="firstName"
          onChange={changeHandler}
        />
      </label>
      <label>
        Last
        <input
          type="text"
          value={state.lastName}
          name="lastName"
          onChange={changeHandler}
        />
      </label>
      <label>
        Country
        <input
          type="text"
          value={state.country}
          name="country"
          onChange={changeHandler}
        />
      </label>
      <label>
        email
        <input
          type="email"
          value={state.email}
          name="email"
          onChange={changeHandler}
        />
      </label>
    </form>
  );
};

ReactDOM.render(
  <React.StrictMode>
    <FormInvite />
  </React.StrictMode>,
  rootElement
);
input {
  width: 3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>

<body>
  <div id="root"></div>
</body>

NOTE: This uses a functional state update to ensure/preserve previous state values that aren't currently being updated.

Alternatively you can create a mapping of state => setState function and use a similar change handler as before, this time though you won't need to merge in updates manually.

const FormInvite = props => {
  const [firstName, setFirstName] = React.useState("");
  const [lastName, setLastName] = React.useState("");
  const [country, setCountry] = React.useState("");
  const [email, setEmail] = React.useState("");

  const fieldSetStateMap = {
    firstName: setFirstName,
    lastName: setLastName,
    country: setCountry,
    email: setEmail
  };

  const changeHandler = e => {
    const { name, value } = e.target;
    fieldSetStateMap[name](value);
  };
  ...

const rootElement = document.getElementById("root");

const FormInvite = props => {
  const [firstName, setFirstName] = React.useState("");
  const [lastName, setLastName] = React.useState("");
  const [country, setCountry] = React.useState("");
  const [email, setEmail] = React.useState("");

  const fieldSetStateMap = {
    firstName: setFirstName,
    lastName: setLastName,
    country: setCountry,
    email: setEmail
  };

  const changeHandler = e => {
    const { name, value } = e.target;
    fieldSetStateMap[name](value);
  };

  React.useEffect(() => {
    console.log("firstname changed", firstName);
  }, [firstName]);
  React.useEffect(() => {
    console.log("lastname changed", lastName);
  }, [lastName]);
  React.useEffect(() => {
    console.log("country changed", country);
  }, [country]);
  React.useEffect(() => {
    console.log("email changed", email);
  }, [email]);

  return (
    <form>
      <label>
        First
        <input
          type="text"
          value={firstName}
          name="firstName"
          onChange={changeHandler}
        />
      </label>
      <label>
        Last
        <input
          type="text"
          value={lastName}
          name="lastName"
          onChange={changeHandler}
        />
      </label>
      <label>
        Country
        <input
          type="text"
          value={country}
          name="country"
          onChange={changeHandler}
        />
      </label>
      <label>
        email
        <input
          type="email"
          value={email}
          name="email"
          onChange={changeHandler}
        />
      </label>
    </form>
  );
};

ReactDOM.render(
  <React.StrictMode>
    <FormInvite />
  </React.StrictMode>,
  rootElement
);
input {
  width: 3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>

<body>
  <div id="root"></div>
</body>

NOTE: This can use a plain state update as each "piece of state" is fully independent of other state and the value is replaced each onChange.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.