Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 90

Displaying Order Confirmation and Error Messages

Recommended resources

Hello and welcome back!

In our last lesson, we successfully connected the "Place Order" button to your Laravel backend, sending the complete order details via an API call. While functional, the user experience is still a bit raw—success is only indicated by moving to the next step, and errors are handled with a simple alert().

Today, we'll complete the checkout flow by implementing professional user feedback. Our goal is to handle the API response to display a proper order confirmation or a helpful error message to the user. This final touch is crucial for building trust and ensuring a smooth user journey.

1. Displaying a Dynamic Order Confirmation

A successful order should result in more than just a generic "thank you." It should provide concrete confirmation, like an order number. Your Laravel endpoint already returns a mock orderId. Let's use it.

First, we need a place to store this orderId in our Checkout.jsx component.

  1. Add a new state variable to hold the order ID.
  2. Update the final "Thank you" step to display it.
// src/pages/Checkout.jsx
import React, { useState, useContext } from 'react';
import { Alert, /* other imports */ } from '@mui/material'; // Import Alert for later
// ... other imports

export default function Checkout() {
  const [activeStep, setActiveStep] = useState(0);
  const [orderId, setOrderId] = useState(null); // State for the order ID
  // ... other state and context hooks

  // ... handleNext, handleBack functions

  async function processOrder(formData) {
    // ... create orderPayload
    
    try {
      const response = await axios.post('http://127.0.0.1:8000/api/orders', orderPayload);

      console.log('Server response:', response.data);
      
      setOrderId(response.data.orderId); // <-- Store the order ID from the response
      clearCart();
      handleNext();

    } catch (error) {
      // We will implement this part shortly
      console.error('Failed to submit order:', error);
    }
  }

  // ... handleFormSubmit function

  return (
    <Container /* ...props */>
      <Paper /* ...props */>
        {/* ... Stepper */}
        
        {activeStep === steps.length ? (
          <React.Fragment>
            <Typography variant="h5" gutterBottom>
              Thank you for your order.
            </Typography>
            {orderId && ( // <-- Conditionally render the confirmation
              <Typography variant="subtitle1">
                Your order number is #{orderId}. We have emailed your order
                confirmation, and will send you an update when your order has
                shipped.
              </Typography>
            )}
          </React.Fragment>
        ) : (
          // ... FormProvider and form
        )}
      </Paper>
    </Container>
  );
}

This pattern of setting state after a successful API call and then conditionally rendering a success message is very common in React. The following resource shows a similar implementation.

Mastering API Requests in React.js

This article, 'Mastering API Requests in React.js', provides excellent, straightforward examples of handling API responses. The CreatePostForm example is particularly relevant here.

Please review the code for the CreatePostForm component under the section 'Sending Data with POST Requests'. Notice how it uses a successMessage state variable, which is set upon a successful response and then used to render a confirmation message. This is exactly the principle we've just applied for our orderId.


2. Understanding and Handling API Errors

Now for the more complex part: handling failures. An error isn't just an error; its cause can vary. Was it a network issue? A server crash? Invalid data? Providing specific feedback is key.

When using Axios, the error object in a catch block contains valuable information that helps us diagnose the problem.

Axios in React: A Guide for Beginners

To effectively handle errors from Axios, it's crucial to understand the structure of the error object it provides. This article from GeeksforGeeks breaks it down clearly.

Please read the sections titled 'Handling Errors in Axios' and 'Error Object'. Focus on the if (error.response) { ... } else if (error.request) { ... } block. This shows how to distinguish between server errors, network errors, and other setup issues. Also, note the properties of the error object described.

As the article highlights, we can differentiate:

  • error.response: The server responded, but with an error status code (like 404, 422, or 500). The useful data is in error.response.data.
  • error.request: The request was sent, but no response was received (e.g., a network error or the backend server is down).
  • error.message: Something else went wrong when setting up the request.

Your Laravel backend, for instance, will typically send validation errors with a 422 Unprocessable Entity status and a JSON body detailing which fields failed. While we won't implement field-specific errors today, understanding the structure is vital.

This next video demonstrates how different backend error structures can be parsed on the frontend.

React.js: Handle API/backend response error (validation errors, bad request error)

Backend APIs, especially for form submissions, often return structured validation errors. This video does an excellent job of showing what these responses look like and how to parse them in React.

Watch the following segments: from 00:42 to 03:00, and from 10:31 to 13:41. Pay attention to how the backend can return different error structures (an array of errors vs. an object of errors) and how the frontend code needs to adapt. This directly relates to how you might handle validation responses from your own Laravel API.


3. Implementing Error Display in the Checkout

Armed with this knowledge, let's update our processOrder function to set a user-friendly error message.

  1. Add a state variable for the submission error.
  2. Implement the catch block to parse the Axios error.
  3. Display the error message in the UI using Material-UI's <Alert> component.

Here is the final version of the code for Checkout.jsx:

// src/pages/Checkout.jsx
import React, { useState, useContext } from 'react';
import axios from 'axios';
import { useForm, FormProvider } from 'react-hook-form';
import { Container, Paper, Stepper, Step, StepLabel, Button, Typography, Box, Alert } from '@mui/material'; // Make sure Alert is imported
import { CartContext } from '../context/CartContext';

// ... (imports for ShippingForm, PaymentForm, etc.)

const steps = ['Shipping address', 'Payment details', 'Review your order'];

// ... (getStepContent function)

export default function Checkout() {
  const [activeStep, setActiveStep] = useState(0);
  const [orderId, setOrderId] = useState(null);
  const [submissionError, setSubmissionError] = useState(null); // State for the error message
  const { cart, clearCart } = useContext(CartContext);
  const methods = useForm();

  const handleNext = () => setActiveStep(activeStep + 1);
  const handleBack = () => setActiveStep(activeStep - 1);

  async function processOrder(formData) {
    setSubmissionError(null); // Clear previous errors on a new attempt

    const orderPayload = {
      // ... (your orderPayload object)
    };

    try {
      const response = await axios.post('http://127.0.0.1:8000/api/orders', orderPayload);
      setOrderId(response.data.orderId);
      clearCart();
      handleNext();
    } catch (error) {
      let errorMessage = 'An unexpected error occurred. Please try again.';
      if (error.response) {
        // The server responded with a status code that falls out of the range of 2xx
        console.error('Server Error:', error.response.data);
        // Use the message from the backend if available, otherwise a generic one
        errorMessage = error.response.data.message || `Server error: ${error.response.status}. Please try again later.`;
      } else if (error.request) {
        // The request was made but no response was received
        console.error('Network Error:', error.request);
        errorMessage = 'Could not connect to the server. Please check your network connection.';
      } else {
        // Something happened in setting up the request that triggered an Error
        console.error('Request Setup Error:', error.message);
        errorMessage = error.message;
      }
      setSubmissionError(errorMessage);
    }
  }

  const handleFormSubmit = (data) => {
    if (activeStep === steps.length - 1) {
      processOrder(data);
    } else {
      handleNext();
    }
  };

  return (
    <Container /* ...props */>
      <Paper /* ...props */>
        {/* ... Stepper */}
        
        {activeStep === steps.length ? (
          <React.Fragment>
            <Typography variant="h5" gutterBottom>
              Thank you for your order.
            </Typography>
            {orderId && (
              <Typography variant="subtitle1">
                Your order number is #{orderId}. We have emailed your order
                confirmation, and will send you an update when your order has
                shipped.
              </Typography>
            )}
          </React.Fragment>
        ) : (
          <FormProvider {...methods}>
            <form onSubmit={methods.handleSubmit(handleFormSubmit)}>
              {getStepContent(activeStep)}
              <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', mt: 3 }}>
                {/* Display the error message here */}
                {submissionError && (
                  <Alert severity="error" sx={{ width: '100%', mb: 2 }}>
                    {submissionError}
                  </Alert>
                )}
                <Box>
                  {activeStep !== 0 && (
                    <Button onClick={handleBack} sx={{ mt: 3, ml: 1 }}>
                      Back
                    </Button>
                  )}
                  <Button
                    variant="contained"
                    type="submit"
                    sx={{ mt: 3, ml: 1 }}
                  >
                    {activeStep === steps.length - 1 ? 'Place order' : 'Next'}
                  </Button>
                </Box>
              </Box>
            </form>
          </FormProvider>
        )}
      </Paper>
    </Container>
  );
}

Conclusion

This lesson marks the completion of our e-commerce checkout project! You've successfully built a system where a user can finalize their purchase, and the application provides clear, professional feedback for both successful orders and potential errors.

Key Takeaways:

  • State for API Responses: Use React state (useState) to store and display dynamic data from API responses, such as an order confirmation number or an error message.
  • Robust Error Parsing: Don't treat all errors the same. By inspecting the Axios error object (error.response, error.request), you can provide users with much more specific and helpful feedback.
  • User-Centric Feedback: A clear confirmation message builds confidence, while a specific error message helps the user resolve the issue, dramatically improving the overall user experience.
  • Cleanup on Retry: Always clear previous error messages when the user attempts the action again to avoid confusion.

Preview of the Next Module:

You have now built a substantial, full-stack application feature. This experience provides a fantastic foundation for diving into more advanced React concepts. In the next module, we will explore some of React's more powerful hooks, starting with useReducer and useContext, which allow us to build more scalable and maintainable state management solutions for even larger applications.