Part 63: Enhancing User Experience: Error Handling and Loading States in React Forms

[Pages] Authentication

[Pages] Authentication

In our journey to build a robust Sign-In form, we've successfully connected our form to an API, allowing users to log in with their credentials. However, until now, if a user entered incorrect credentials, the application did not provide any feedback, leading to a poor user experience. Today, we're going to address this by adding error handling and improving the user interface to manage loading states.

Handling API Request Errors

When a user submits the form with incorrect credentials, we want to display an informative error message. Let's implement this feature.

Step 1: Introduce a Status State

We'll add a state variable to manage both loading and error states.

// pages/sign-in.js

import { useState } from 'react';
import Button from '../components/Button';
import Field from '../components/Field';
import Input from '../components/Input';
import Page from '../components/Page';
import { fetchJson } from '../lib/api';

function SignInPage() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [status, setStatus] = useState({ loading: false, error: false });

  const handleSubmit = async (event) => {
    event.preventDefault();
    setStatus({ loading: true, error: false });
    try {
      const response = await fetchJson('http://localhost:1337/auth/local', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ identifier: email, password }),
      });
      setStatus({ loading: false, error: false });
      console.log('sign in:', response);
    } catch (err) {
      setStatus({ loading: false, error: true });
    }
  };

  return (
    <Page title="Sign In">
      <form onSubmit={handleSubmit}>
        <Field label="Email">
          <Input type="email" required value={email}
            onChange={(event) => setEmail(event.target.value)}
          />
        </Field>
        <Field label="Password">
          <Input type="password" required value={password}
            onChange={(event) => setPassword(event.target.value)}
          />
        </Field>
        {status.error && (
          <p className="text-red-700">
            Invalid credentials
          </p>
        )}
        {status.loading ? (
          <p>Loading...</p>
        ) : (
          <Button type="submit">
            Sign In
          </Button>
        )}
      </form>
    </Page>
  );
}

export default SignInPage;

Explanation:

  • Status State: We use a single status state variable to track both loading and error conditions.

  • Error Handling: We use a try-catch block to handle errors. If an error occurs, status.error is set to true.

  • Conditional Rendering: We conditionally render an error message if status.error is true.

Managing Loading States

To prevent multiple form submissions while waiting for a response, we will disable the "Sign In" button during the loading phase.

Step 2: Implement Loading Indicator and Disable Button

  • Loading State: We set status.loading to true when the request is in progress.

  • Button Disabled: The "Sign In" button is replaced with a "Loading..." text during the request.

Explanation:

  • Loading Indicator: Display "Loading..." to inform users that a request is being processed.

  • Prevent Multiple Submissions: Disabling the button prevents users from submitting the form multiple times.

Simulating a Slow Network

To simulate a slow network response and test our loading indicator, we can artificially introduce a delay. This helps us ensure that the loading state is handled correctly.

Add a Sleep Function

// lib/api.js

export const sleep = (milliseconds) => {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
};

// Usage: await sleep(2000);

Explanation:

  • Sleep Function: Converts setTimeout into a Promise, allowing us to await a delay. This simulates network latency during development.

Conclusion

With these enhancements, our Sign-In form provides clear feedback to users when errors occur and manages loading states effectively. This significantly improves the user experience by preventing multiple submissions and displaying error messages when necessary.

In our next step, we'll focus on handling the JSON web token received upon successful login and deciding how to store and use it within our application. Stay tuned for more improvements!

Last updated