eolas/neuron/2abd5c63-27cc-420e-b0df-76bdd7f8acb1/Mutations_with_Apollo_Client.md

195 lines
5.3 KiB
Markdown
Raw Normal View History

2024-12-09 18:34:15 +00:00
---
tags: [graphql]
---
# Mutations with Apollo Client
Queries are read-only operations. Mutations are write-only operations.
Just like the `Query` type, the `Mutation` type serves as an entrypoint to the
schema.
## Naming convention
- Use verb: `add`, `create`, `delete`
- Refer to the datatype
For example `addSpaceCat(){}`
## Demonstration mutation
We are going to create a mutation that increments the `numberOfViews` field on
the `Track` type:
## Updating the schema
```js
type Mutation {
incrementTrackViews(id: ID!): IncrementTrackViewsResponse!
}
// Define a specific response type that specifically matches our needs
type IncrementTrackViewsResponse {
code: Int! // status code
success: Boolean! // whether mutation was successful
message: String! // what to say if mutation successful
track: Track // not nullable because might error
}
```
Based on this schema, the mutation will recieve a `Track` id and increment the
specified `Track`. It will return an object comprising the newly updated `Track`
and a bundle of properties that provide feedback on the status of the
operations: a status code, whether it succeeded, and a message.
## Updating the data source
Remember that our sole data source in the demonstration project is a REST API.
We handle it within GraphQL using Apollos `RESTDataSource` class. We need to add
a method to this class that will increment the track views. We wil use the
`PATCH` REST method:
```js
class TrackAPI extends RESTDataSource {
constructor() {...}
getTracksForHome() {...}
getAuthor(authorId) {...}
getTrack(trackId){...};
incrementTrackViews(trackId) {
return this.patch(`track/${trackId}/numberOfViews`);
}
}
```
The `patch()` method is procided by the `RESTDataSouce` class that `TrackAPI`
inherits from
## Adding resolver
Next we need a resolver that corresponds to the mutation we have defined in the
schema. We will need to handle successful responses as well as errors.
### Success case
As always we match the shape of the schema:
```js
const resolvers = {
Query: {
// ...query resolvers
},
Mutation: {
// increments a track's numberOfViews property
incrementTrackViews: async (_, { id }, { dataSources }) => {
const track = await dataSources.trackAPI.incrementTrackViews(id);
return {
code: 200,
success: true,
message: `Successfully incremented number of views for track ${id}`,
track,
};
},
},
};
```
There's more going on with this resolver than the previous one. As is standard,
we call the API using the `TrackAPI` class. However we don't just immediately
return this when it executes. This is because the schema specifies that the
return type `IncrementTrackViewsResponse` requires more than just the updated
`Track`. So we wait this and return it with the cluster of metadata about the
mutation response (`code`, `success`, and `message`).
### Error case
We can extend the Mutation resolver to allow for errors. We'll do this by
refactoring the resolver into a `try...catch` block and adding the error
handling in the `catch`.
We'll harness the details that are provided by Apollos' own `err` object which
is returned by the `RESTDataSource` class that our resolver ultimately traces
back to:
```js
const resolvers = {
Query: {
// ...query resolvers
}
Mutation: {
incrementTrackViews: async (_, {id}, {dataSources}) => {
try {
const track = await dataSources.trackAPI.incrementTrackViews(id);
return {
code: 200,
success: true,
message: `Successfully incremented number of views for track ${id}`,
track
};
} catch (err) {
return {
code: err.extensions.response.status,
success: false,
message: err.extensions.response.body,
track: null
};
}
},
}
}
```
## The `useMutation` hook
We invoke the `useMutation` hook to issue mutations from the client-side.
As with queries and
[query constants](Apollo_Client.md#query-constants) we
wrap our mutation in a `gql` template string:
```js
const INCREMENT_TRACK_VIEWS = gql`
mutation IncrementTrackViews($incrementTrackViewsId: ID!) {
incrementTrackViews(id: $incrementTrackViewsId) {
code
success
message
track {
id
numberOfViews
}
}
}
`;
```
We then pass it to the `useMutation` hook including an options object with our
variables. (This time the specific variable is named):
```js
import { gql, useMutation } from "@apollo/client";
useMutation(INCREMENT_TRACK_VIEWS, {
variables: { incrementTrackViewsId: id },
});
```
`useMutation` returns an array of two elements:
1. The mutation function that actually executes
2. An object comprising (`loading`, `error`, `data`) - this is the same as is
the return value of `useQuery`.
So we can destructure like so (we don't always need the second element);
```js
const [incrementTrackViews, dataObject] = useMutation(INCREMENT_TRACK_VIEWS...)
```
Given that we can isolate the mutation function as the first destructured
element of the array, we could then attach `incrementTrackViews` to a button or
other frontend interaction.