GraphQL Mutations and Form Errors
GraphQL has a mechanism for errors. It looks like this:
{
"data": {
"myMutation": null
},
"errors": [
{
"message": "Name is invalid",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"myMutation.name"
]
}
]
}
The error messages are more developer focused. Showing them directly to the user would be confusing.
If we have a form like the following:
Using the build in GraphQL errors for this form has the following drawbacks:
- some fields might have more than one error
- separating between input validation error and general error
- mapping form fields with errors
It is a lot easier to handle errors as regular fields. In Product Hunt all mutations have the following format:
# `$input` is part of Relay specification
mutation MyMutation($input: MyMutationInput!) {
# remaping to `response` is optional, just for handling with forms
response: myMutation(input: $input) {
# `node` is the record affected by mutation
node {
...dataWeCareFromResponse
}
# array of validation errors
errors {
name
messages
}
}
}
The SDL for the mutation is:
type Mutation {
myMutation(input: MyMutationInput!): MyMutationPayload!
}
type Error {
field: String!
messages: [String!]!
}
input MyMutationInput {
# ...inputs
}
type MyMutationPayload {
node: # ... affected record
errors: [Error]!
}
Since the format of all Form mutations is known. Building generic tooling around it is straightforward.
The following is the code Form
used in Product Hunt.
class Form extends React.Component {
render() {
return (
<form onSubmit={this.handleSubmit}>
{this.props.children}
</form>
);
}
handleSubmit = async (formData) => {
// ... guards and setup
const { response: { node, errors } } = await this.props.submit(formData);
if (errors.length > 0) {
this.setState({ errors: normalizeErrors(errors) });
} else {
this.props.onSuccess(node);
}
// ... clean up
};
// ... more form code
}