scarf

LitmusChaosCon 2024 on September 12th! Register now(Free) or submit a talk proposal submit a talk proposal(last date: June 24th)

Blog

>

How to use React hooks in Apollo Client for GraphQL

Saranya Jena

Saranya Jena

Author

22nd September 2020

7 Minute Read

How to use React hooks in Apollo Client for GraphQL

Hello World! In this blog, I am going to discuss how we can use React hooks with Apollo to connect to GraphQL API in different scenarios. Assuming that you have a basic understanding of the same, I will be explaining how GraphQL data can be shared with the UI using React hooks by giving a few examples that we are already using in our ongoing project, the Litmus Portal.

GraphQL

#What is GraphQL?

Before going forward, let me give a very brief overview of GraphQL and what all things we are going to discuss. So GraphQL is a query language for APIs that is developed by Facebook. It is an efficient alternative to REST because of its features such as:

  • With GraphQL there's no over fetching or under fetching of data, unlike REST.
  • Strongly typed graphQL schema which can be written in GraphQL Schema Definition Language (SDL) helps you validate your API requests during its compile time.
  • With the development of various GraphQL libraries (Apollo, Relay, etc) you are getting a lot of features such as caching, realtime data, etc.
  • It provides a large and amazing community! You can always get your queries answered whenever stuck.

This was just a basic introduction to GraphQL, but I recommend you to visit the site to gain deeper insights of the same.

#What we'll do?

I will be mainly focussing on the front-end side, where I am gonna explain the two very useful react hooks useQuery and useMutation, how are we using these in our project for GraphQL operations along with the code.

##Wait...what are GraphQL operations?##

GraphQL provides various types of operations such as Query, Mutation, and Subscription which act as the entry points for the requests sent by the client. In this blog, I'll be discussing the first two types i.e. Query and Mutation.

  • Query: useQuery hook is used to fetch the data from the server and attach it to the UI. To run a query you need to call this hook by passing the query string, it returns an object from Apollo client containing data, error, loading properties which change their values after execution. The hook is called when the component renders and the above properties can be used conditionally to render the UI.

Basic syntax:

const { loading, error, data } = useQuery<Type1, Type2>(
    QUERY_STRING,
    { variables: <variable>,
onCompleted:()=>{console.log("query successful",data);}
,
onError:(error)=>{console.error(error);},
});
  1. data : The required data we are getting after the query is successful.
  2. loading : It is a boolean value, if true, it means the query is still in flight. After it is successful the value of loading changes to false.
  3. error : It stores the error if occurred while querying.
  • Mutation: useMutation hook is used to send updates to the GraphQL server as a result of which data can be updated in the back-end. It is somewhat similar to useQuery in terms of syntax with some minor differences. To execute a mutation, you need to pass the mutation string to the hook. This hook returns a tuple containing a mutate function which can be called whenever its execution is required and an object having certain fields that represent the current status of mutation's execution.

Basic syntax:

 const [mutateFunction,{ error,loading}] = useMutation<Type>(MUTATION_STRING, {
    onCompleted: () => {
    console.log("details updated")
    },
    onError: (error) => {
    onError:(error)=>console.error(error);
    },
    refetchQueries: [{ query: QUERY_STRING, variables: <variable>],
  });
  1. mutateFunction : It is the mutate function which can be called anytime to run the mutation.
  2. The second parameter is the object representing the mutation's execution status such as error , loading which have been explained above.

In both examples, I have added options to the hooks:

  1. onCompleted : It is a callback executed after a successful query/mutation.
  2. onError : Callback executed in case of an occurrence of error.
  3. refetchQueries : It takes an array or function which is used to specify a list of queries that need to be refetched after the mutation is successful.

best practices

###Some of the good practices you can follow:###

  • Type the data you are sending or receiving during the requests wherever it is required. It enhances readability and understandability.
  • As a beginner we often tend to store the data we received from the requests in local states which is not required. Apollo Client provides an in-memory cache in which it stores the data that helps the client to respond to future queries for the same data without sending unnecessary requests. So instead of storing it in local states we can directly access and use it without making repeated requests.

Now I'll be explaining some examples that we have used in our ongoing project, the Litmus Portal.

Litmus

#LitmusChaos LitmusChaos is an open-source toolset to practice chaos engineering in cloud-native systems. It comes up with a large set of chaos experiments that are hosted on the hub. For further details, you can check out our github repo. Litmus Portal provides a console and UI experience for managing, monitoring, and events around chaos workflows. It is being developed using React and TypeScript for the front-end and Golang for the back-end.

#Examples Without any further delay, let's get started!!

coding cat

Since the examples I am going to explain are a part of a project, I have excluded some parts of the logic which are not relevant to the blog. You can find the complete code here.

###Query###

Schema

export const GET_USER = gql`
  query getUser($username: String!) {
    getUser(username: $username) {
      username
      email
      id
      name
      projects {
        members {
          user_id
          user_name
          role
          invitation
          name
          email
          joined_at
        }
        name
        id
      }
      company_name
      updated_at
      created_at
      removed_at
      is_email_verified
      state
      role
    }
  }
`;

export const ALL_USERS = gql`
  query allUsers {
    users {
      id
      name
      username
      email
    }
  }
`;

The GET_USER query string returns the complete details of a user whose username is passed as a variable. The ALL_USERS query string returns a list of all users who are present along with their details including id, name, username, and email.

useQuery

const { data: dataB } = useQuery<CurrentUserDetails, CurrentUserDedtailsVars>(
    GET_USER,
    { variables: { username: userData.username } }
  );

 const { data: dataA } = useQuery(ALL_USERS, {
    skip: !dataB,
    onCompleted: () => {
    
    //consoles the list of all users present
    console.log(dataA.users);
    },
    onError: (error) => {
    //in case of error, it prints the error message in the console
    console.error(error.message)
  });

In the above example, I have two queries:

  • GET_USER : I am sending the username as variable to get all the details associated with that username. The received data can be accessed through dataB. CurrentUserDedtailsVars is the type of data I am sending i.e. the username and CurrentUserDetails is the type of data I am receiving on a successful query. These types are stored in a separate file:
export interface Member {
  user_id: string;
  user_name: string;
  role: string;
  invitation: string;
  name: string;
  email: string;
  joined_at: string;
}

export interface Project {
  members: Member[];
  name: string;
  id: string;
}

export interface UserDetails {
  username: string;
  projects: Project[];
  name: string;
  email: string;
  id: string;
  company_name: string;
  updated_at: string;
  created_at: string;
  removed_at: string;
  is_email_verified: string;
  state: string;
  role: string;
}

export interface CurrentUserDetails {
  getUser: UserDetails;
}

export interface CurrentUserDedtailsVars {
  username: string;
}
  • ALL_USERS : This query is for fetching the list of all the users which can be accessed through dataA.

skip : This is a boolean value, if true, the query will be skipped. In the above logic if dataB is empty i.e. unless and until GET_USER query is successful ALL_USERS query will be skipped. Once dataA gets populated then the second query is executed. This option is useful in the cases where you need to execute the queries in specific order.

###Mutation###

Schema

export const SEND_INVITE = gql`
  mutation sendInvite($member: MemberInput!) {
    sendInvitation(member: $member) {
      user_id
      user_name
      role
      invitation
    }
  }
`;

The SEND_INVITE mutation string is used to send an invitation to a user for a selected project. Once the user accepts the invitation he/she becomes a member of that project too. As MemberInput we need to send the data which includes the id of the project, the username of the user whom we are going to send the invitation, the role of user in the project name Viewer or Editor.

useMutation

 // mutation to send invitation to selected users
  const [SendInvite, { error: errorB, loading: loadingB }] = useMutation<
    MemberInviteNew
  >(SEND_INVITE, {
    refetchQueries: [{ query: GET_USER, variables: { username } }],
  });

In the above mutation, once the invitation is sent(mutation is successful), the GET_USER query is refetched to update the data.

MemberInviteNew is the type of data I am sending as variables to mutation string. The interface is defined as follows:

export interface MemberInviteNew {
  member: {
    project_id: string;
    user_name: string;
    role: string;
  };
}

SendInvite is the mutate function which can be called whenever you want to execute the mutation.

 SendInvite({
   variables: {
       member: {
             project_id: "1234abc",
             user_name: "john_doe",
             role: "Editor",
               },
              },
            })

#Conclusion#

So these were some of the examples of GraphQL mutation and query. I hope I was able to explain these concepts well, but if you still have some queries or feedback feel free to reach out to me. Since the LitmusChaos project is completely open-source, feel free to contribute in any way possible. Visit the GitHub repo and become one out of the many stargazers.

{% github litmuschaos/litmus %}

Last but not the least, with the upcoming Hacktober Fest, there are many issues for all levels such as good-first issues, front-end issues, complex issues, etc. So even if you are a beginner you can always submit a PR and start contributing to open source. Grab your chance to win a lot of Litmus swags and goodies upon a successful merge. So do not forget to visit the Litmus site and join our community(#litmus channel on the Kubernetes Slack).😇

thankyou

Chaos Engineering made easy

Litmus is highly extensible and integrates with other tools to enable the creation of custom experiments. Kubernetes developers & SREs use Litmus to manage chaos in a declarative manner and find weaknesses in their applications and infrastructure.

Get started with Litmus