Zettelkasten
week01
exercise
My Solution (Forms)

#solution


My solution

import { useState } from "react";
import { validateEmail } from "./utils";

const PasswordErrorMessage = () => {
  return (
    <p className="FieldError">Password should have at least 8 characters</p>
  );
};


function App() {

  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState({
    value: "",
    isTouched: false
  });

  const [role, setRole] = useState("role");

  const getIsFormValid = () => {
    // Implement this function
    return (
      firstName &&
      validateEmail(email) &&
      password.value.length >= 8 &&
      role !== "role"
    );
  };

  const clearForm = () => {
    setFirstName("");
    setLastName("");
    setEmail("");
    setPassword({ value: "", isTouched: false });
    setRole("role");
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    alert("Account created!");
    clearForm();
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <fieldset>
          <h2>Sign Up</h2>
          <div className="Field">
            <label>
              First name <sup>*</sup>
            </label>
            <input
              value={firstName}
              onChange={(e) => setFirstName(e.target.value)}
              placeholder="First name"
            />
          </div>
          <div className="Field">
            <label>Last name</label>
            <input
              value={lastName}
              onChange={(e) => setLastName(e.target.value)}
              placeholder="Last name"
            />
          </div>
          <div className="Field">
            <label>
              Email address <sup>*</sup>
            </label>
            <input
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="Email address"
            />
          </div>
          <div className="Field">
            <label>
              Password <sup>*</sup>
            </label>
            <input
             value={password.value}
             type="password"
             onChange={(e) => {
               setPassword({ ...password, value: e.target.value });
             }}
             onBlur={() => {
               setPassword({ ...password, isTouched: true });
             }}
             placeholder="Password"
           />
           {password.isTouched && password.value.length < 8 ? (
             <PasswordErrorMessage />
           ) : null}
          </div>
          <div className="Field">
            <label>
              Role <sup>*</sup>
            </label>
            <select value={role} onChange={(e) => setRole(e.target.value)}>
              <option value="role">Role</option>
              <option value="individual">Individual</option>
              <option value="business">Business</option>
            </select>
          </div>
          <button type="submit" disabled={!getIsFormValid()}>
            Create account
          </button>
        </fieldset>
      </form>
    </div>
  );
}

export default App;

Step 1


The first step involves converting all form elements into controlled components. Since the pieces of local state have been already defined at the top of the component, you just have to assign each state piece to the value prop from each input element. To be able to account for state updates, each input should also define the onChange prop and call the state setter with the value property from the event target as parameter.

The password input is a special case that has an object as state instead of a string. As a result, the state setter should spread the previous values so they don’t get overridden. Finally, to make sure the password characters are obscured, you need to use the type “password” for the input.

<div className="Field"
 
 <label
   First name <sup>*</sup
 </label
 
 <input 
   value={firstName} 
   onChange={(e) => { 
     setFirstName(e.target.value); 
   }} 
   placeholder="First name" 
 /> 
</div>
 
<div className="Field"
 <label>Last name</label
 <input 
   value={lastName} 
 
   onChange={(e) => { 
     setLastName(e.target.value); 
   }} 
   placeholder="Last name" 
 /> 
</div
 
<div className="Field"
 <label
   Email address <sup>*</sup
 </label
 
 <input 
   value={email} 
   onChange={(e) => { 
     setEmail(e.target.value); 
   }} 
   placeholder="Email address" 
 /> 
</div
 
<div className="Field"
 <label
   Password <sup>*</sup
 </label
 <input 
   value={password.value} 
   type="password" 
   onChange={(e) => { 
     setPassword({ ...password, value: e.target.value }); 
   }} 
   placeholder="Password" 
 /> 
</div
<div className="Field"
 <label
   Role <sup>*</sup
 </label
 <select value={role} onChange={(e) => setRole(e.target.value)}> 
   <option value="role">Role</option
   <option value="individual">Individual</option
   <option value="business">Business</option
 </select
</div>

Step 2


The isTouched property on the password state was defined to determine when the input was touched at least once. In order to listen for interactions, form inputs have two additional events you can subscribe to: onFocus and onBlur.

In this scenario, you need to use the onBlur event, which is called whenever the input loses focus, so that guarantees the user has interacted with the password input at least once. In that event, you should set the isTouched property to true with the password state setter.

Then, the condition to display the error message relies on that value being true and a check on the password length to see if it’s less than 8 characters long. If the condition is true, the component PasswordErrorMessage should be rendered. The final code should be as follows:

<div className="Field">
     
  <label>
         Password <sup>*</sup>   
  </label>
     
  <input
    value={password.value}
    type="password"
    onChange={(e) => {
      setPassword({ ...password, value: e.target.value });
    }}
    onBlur={() => {
      setPassword({ ...password, isTouched: true });
    }}
    placeholder="Password"
  />
     {password.isTouched && password.value.length < 8 ? <PasswordErrorMessage /> : null
</div>

If implemented correctly, the form should display an error message below the password field:

Screenshot from sign up interface

Step 3


To prevent the default behavior of the form when clicking on the submit button, you have to call preventDefault on the event object, right in your submit handler function.

const handleSubmit = (e) => {
  e.preventDefault();
  alert('Account created!');
  clearForm();
};

Step 4


To fulfil the validation rules of the form, the body of the getIsFormValid function should be implemented as below:

const getIsFormValid = () => {
  return firstName && validateEmail(email) && password.value.length >= 8 && role !== 'role';
};

Below is an example of a valid form:

Sign up page interface

Step 5


Finally, to clear the form state after a successful submission, you should set each piece of state to its initial value:

const clearForm = () => {
  setFirstName('');
  setLastName('');
  setEmail('');
  setPassword({
    value: '',
    isTouched: false,
  });
  setRole('role');
};