Reputation: 1445
I have the following GraphQL operation that works:
GraphQL Mutation:
mutation CreateUserMutation ($input: CreateUserInput!) {
createUser(input: $input) {
clientMutationId
userEdge {
node {
email
username
}
},
validationErrors {
id
email
}
}
}
GraphQL mutation response:
{
"data": {
"createUser": {
"clientMutationId": "112",
"userEdge": {
"node": {
"email": "[email protected]",
"username": "soosap112"
}
},
"validationErrors": {
"id": "create-user-validation-errors",
"email": [
"Email looks not so good."
]
}
}
}
}
So far so good, my GraphQL response's validationErrors
is an object of key-value pairs, where the value is always an array of input validation error messages from the server w/ respect to a particular input field (i.e. {'email': ['email is taken', 'email is on blacklist']
}).
Next step (and this is where I need help) - how to consume that data in the Relay client store? In other words, what to do to have the validationErrors available in my component as this.props.validationErrors
?
CreateUserMutation.js
import Relay from 'react-relay';
export class CreateUserMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`
mutation {
createUser
}
`;
}
getVariables() {
return {
email: this.props.email,
username: this.props.username,
password: this.props.password,
};
}
getFatQuery() {
return Relay.QL`
fragment on CreateUserPayload @relay(pattern: true) {
userEdge,
validationErrors,
viewer { userConnection }
}
`;
}
getConfigs() {
return [
{
type: 'FIELDS_CHANGE',
fieldIDs: {
validationErrors: 'create-user-validation-errors',
},
},
{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'userConnection',
edgeName: 'userEdge',
rangeBehaviors: {
// When the ships connection is not under the influence
// of any call, append the ship to the end of the connection
'': 'append',
// Prepend the ship, wherever the connection is sorted by age
// 'orderby(newest)': 'prepend',
},
},
];
}
}
Here is my attempt: First of all I am able to consume user edges into my Relay client store by using the getConfigs RANGE_ADD
. Since my validationErrors
object does not implement the connection model, the FIELDS_CHANGE
seemed to be the only reasonable type in my case. I am trying to mock the dataID that Relay seems to require to populate the client store using 'create-user-validation-errors' as a unique id.
Here is a snippet from my React component to make the example complete.
class App extends React.Component {
static propTypes = {
limit: React.PropTypes.number,
viewer: React.PropTypes.object,
validationErrors: React.PropTypes.object,
};
static defaultProps = {
limit: 5,
validationErrors: {
id: 'create-user-validation-errors',
},
};
handleUserSubmit = (e) => {
e.preventDefault();
Relay.Store.commitUpdate(
new CreateUserMutation({
email: this.refs.newEmail.value,
username: this.refs.newUsername.value,
password: this.refs.newPassword.value,
viewer: this.props.viewer,
})
);
};
How do I consume a non-connection model based piece of information from a GraphQL response in a React component using Relay? Minimum working example would be awesome.
Do you know of a better way of doing server-side input validation using GraphQL and Relay?
Upvotes: 3
Views: 1245
Reputation: 3399
A mutation either succeeds or fails. I usually design the client-side mutation's fat query and configs, keeping the success case in mind. To handle failure case, the mutation's callback functions are sufficient.
There are two ways that I know of.
1) Using FIELDS_CHANGE
type in getConfigs
function of client-side mutation code: When we need to update data in Relay store in response to the mutation's result, this is usually used. The code looks like the following:
getFatQuery() {
return Relay.QL`
fragment on CreateUserPayload {
...
...
viewer {
userCount,
}
}
`;
}
getConfigs() {
return [
{
type: 'FIELDS_CHANGE',
fieldIDs: {
viewer: this.props.viewer.id,
},
},
...
...
];
}
In your case, if you want to update validationErrors
as part of the Relay store, pass it as a prop and have validationErrors: this.props.validationErrors.id
.
2) Using mutation's callback functions: When we just need some information that shouldn't affect the data in Relay store, this is a good option. The code looks like below:
const mutation = new CreateUserMutation({
email: this.state.email,
username: this.state.username,
password: this.state.password,
viewer: this.props.viewer,
});
const onSuccess = (response) => {
// If you have both successful data update and some other extra info, you
// can have, say `response.warnings`. The server sends `warnings` in the
// response object.
};
const onFailure = (transaction) => {
// This is the most appropriate place for dealing with errors.
var error = transaction.getError();
// Get the errors!
};
Relay.Store.commitUpdate(mutation, {onSuccess, onFailure});
The elementary input validation supported by GraphQL and Relay are currently limited to using GraphQLNonNull
and the intended types, for example, names: new GraphQLNonNull(GraphQLString)
. So, if we pass an integer for name, the mutation will fail and appropriate error message will be provided.
For other types of validation, mutateAndGetPayload
function in server-side mutation is a good place, where we can apply our own logic.
Konstantin Tarkus has an excellent article on input validation in GraphQL mutations.
Upvotes: 2