This video is available to students only

Custom useMutation Hook

In this lesson, we'll create a custom useMutation Hook which will abstract the server fetch functionality needed to conduct a mutation from a component.

📝 This lesson's quiz can be found - here. 🗒️ Solutions for this lesson's quiz can be found - here.

We've created a useQuery Hook to help make a GraphQL query request when a component first mounts. In this lesson, we'll look to create a useMutation Hook that helps prepare a function to make an API mutation request.

Gameplan#

Our useMutation Hook will behave differently since we won't want a mutation to run the moment a component mounts by default. Our useMutation Hook will simply receive the mutation query document to be made.

And return an array of two values - the first being the request function and the second being an object that contains detail of the request.

We'll get a better understanding of how our useMutation Hook is to behave once we start to create it.

useMutation#

We'll create our useMutation Hook in a file of its own in the src/lib/api folder. We'll label this file useMutation.ts.

Similar to the useQuery Hook, we're going to need to keep track of some state within our useMutation Hook so we'll import the useState Hook. Since we're going to be interacting with the server as well, we'll import the server object which will help us make the server.fetch() request.

We'll create a State interface that describes the shape of the state object we'll want to maintain. The State interface will have the data, loading, and error fields. The type of data will either be equal to a type variable passed in the interface (TData) or null. The loading and error fields will be of type boolean.

We'll export and create a const function called useMutation. The useMutation function will accept two type variables - TData and TVariables. TData is to represent the shape of data that can be returned from the mutation while TVariables is to represent the shape of variables the mutation is to accept. Both of the TData and TVariables type variables will have a default type value of any.

Our mutation function, however, will only accept a single required document query parameter.

The term query here is used to reference the GraphQL request that is to be made. One can rename the query parameter to mutation to be more specific.

variables#

We expect variables necessary for our request to be used in our useMutation Hook but we haven't specified variables as a potential argument in our function. The reason being is how we want our Hook to work. In our use case, we won't want to pass in the variables when we use the useMutation Hook but instead pass it in the request function the mutation is expected to return.

Let's go through an example of what we intend to do. Assume the useMutation Hook when used in a component is to return a fetch function that we'll label as request.

Only when the request function is called, will we pass in the variables necessary for the mutation.

This is simply how we want to set up our useMutation Hook. It's possible to also pass in variables when we run the Hook function as well.

useMutation#

At the beginning of the useMutation Hook, we'll initialize the state object we'll want our Hook to maintain. We'll initialize our state similar to how we've done in the useQuery Hook by setting data to null and the loading and error fields to false.

We'll now create a fetch() function in our useMutation Hook that will be responsible for making our request. fetch() will be an asynchronous function that accepts a variable object that will have a type equal to the TVariables type variable. We'll also state the variables parameter is an optional parameter since there may be mutations we can create that don't require any variables.

We'll introduce a try/catch statement within the fetch() function.

At the beginning of the try block, we'll set the state loading property to true since at this point the request will be in-flight. We'll keep data and error as the original values of null and false respectively.

We'll then conduct our server.fetch() function, and pass in the query and variables values the server.fetch() function can accept. The server.fetch() function will return an object of data and errors so we'll destruct those values as well. We'll also pass along the type variables of data and variables to ensure the information returned from the server.fetch() function is appropriately typed.

In the last lesson, we observed how GraphQL requests could resolve but have errors be returned from our resolver. These errors will be captured in the errors array we'retrieving from the server.fetch() function. We'll check to see if these errors exist and if so - throw an Error and pass in the error message from the first error object in the errors array.

If the request is successful and no errors exist, we'll set the returned data into our state. We'll also set the loading and error properties to false.

If an error arises for either the request failing or errors being returned from the API, we'll set the error value in our state to true. We'll also specify data should be null and loading is false. We'll capture the error message and look to log the error message in the browser console.

Start a new discussion. All notification go to the author.