Optimistic UI and Error Handling with Apollo Client in React JS

Introduction

Optimistic UI allows you to show a UI update as if the mutation has already been completed, improving the user experience by making it feel faster. In Apollo Client, you can implement Optimistic UI to show changes immediately without waiting for the server response. In this tutorial, we will also explore error handling when using Apollo Client mutations.

Step 1: Setting Up Apollo Client

To get started with Apollo Client, you first need to set up Apollo Client in your React application. If you haven't already, follow the steps below:

Install Apollo Client and GraphQL:


  npm install @apollo/client graphql
      

Create an Apollo Client instance:


  import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
  
  const client = new ApolloClient({
    uri: 'http://localhost:4000/',  // Replace with your GraphQL API endpoint
    cache: new InMemoryCache(),
  });
  
  export default client;
      

Wrap your app with ApolloProvider in your main entry file:


  import { ApolloProvider } from '@apollo/client';
  import client from './apolloClient';
  import App from './App';
  
  ReactDOM.render(
    
      
    ,
    document.getElementById('root')
  );
      

Step 2: Implementing Optimistic UI

Optimistic UI is used to immediately update the UI before a mutation response has been received. You can achieve this by setting the optimisticResponse field in the mutation.

Example: Adding a New User with Optimistic UI

Let's create a mutation that adds a new user to a list, and we will implement Optimistic UI to immediately display the new user:


  import React, { useState } from 'react';
  import { useMutation, gql } from '@apollo/client';
  
  const ADD_USER = gql`
    mutation AddUser($name: String!) {
      addUser(name: $name) {
        id
        name
      }
    }
  `;
  
  const GET_USERS = gql`
    query GetUsers {
      users {
        id
        name
      }
    }
  `;
  
  const AddUserForm = () => {
    const [name, setName] = useState('');
    const [addUser] = useMutation(ADD_USER, {
      optimisticResponse: {
        addUser: {
          id: 'temp-id',  // Temporary ID for optimistic response
          name,
        },
      },
      update(cache, { data: { addUser } }) {
        const { users } = cache.readQuery({ query: GET_USERS });
        cache.writeQuery({
          query: GET_USERS,
          data: { users: users.concat([addUser]) },
        });
      },
    });
  
    const handleSubmit = async (e) => {
      e.preventDefault();
      try {
        await addUser({ variables: { name } });
        setName('');
      } catch (error) {
        console.error("Error adding user:", error);
      }
    };
  
    return (
      
setName(e.target.value)} placeholder="Enter user name" />
); }; export default AddUserForm;

In the above code:

  • optimisticResponse: This field contains the optimistic response, which simulates the result of the mutation before the actual server response.
  • temp-id: We use a temporary ID because the server will generate a real ID once the mutation is completed.
  • update: The update function is used to update the Apollo cache immediately with the optimistic data.

Step 3: Handling Errors in Mutations

Error handling is important to ensure that the UI can gracefully recover from failed mutations. Apollo Client provides the onError callback and the error object in the mutation result.

Example: Handling Errors in the Add User Mutation

We will modify the previous example to handle errors gracefully by showing an error message when the mutation fails:


  import React, { useState } from 'react';
  import { useMutation, gql } from '@apollo/client';
  
  const ADD_USER = gql`
    mutation AddUser($name: String!) {
      addUser(name: $name) {
        id
        name
      }
    }
  `;
  
  const GET_USERS = gql`
    query GetUsers {
      users {
        id
        name
      }
    }
  `;
  
  const AddUserForm = () => {
    const [name, setName] = useState('');
    const [errorMessage, setErrorMessage] = useState('');
    const [addUser] = useMutation(ADD_USER, {
      optimisticResponse: {
        addUser: {
          id: 'temp-id',
          name,
        },
      },
      update(cache, { data: { addUser } }) {
        const { users } = cache.readQuery({ query: GET_USERS });
        cache.writeQuery({
          query: GET_USERS,
          data: { users: users.concat([addUser]) },
        });
      },
      onError(error) {
        setErrorMessage("Failed to add user. Please try again.");
        console.error("Error adding user:", error);
      },
    });
  
    const handleSubmit = async (e) => {
      e.preventDefault();
      try {
        setErrorMessage(''); // Reset error message before mutation
        await addUser({ variables: { name } });
        setName('');
      } catch (error) {
        console.error("Error adding user:", error);
      }
    };
  
    return (
      
setName(e.target.value)} placeholder="Enter user name" />
{errorMessage &&

{errorMessage}

}
); }; export default AddUserForm;

In the updated example:

  • onError: The onError function is used to capture any error during the mutation and set an error message.
  • errorMessage: A state variable is used to display a message if an error occurs during the mutation.

Step 4: Running Your Application

After implementing Optimistic UI and error handling, run your React app:


  npm start
      

Now, when you add a user, the UI will update immediately with the optimistic response. If the mutation fails, an error message will be displayed.

Conclusion

In this tutorial, we learned how to:

  • Implement Optimistic UI with Apollo Client to immediately reflect changes in the UI.
  • Handle errors in mutations using Apollo Client’s error handling mechanism.
  • Use the optimisticResponse and onError to enhance the user experience and manage errors effectively.

By combining Optimistic UI and error handling, you can make your React app feel faster and more resilient to failures.





Advertisement