我正在尝试使用 Relay Todo 示例中的修改代码设置突变。
当我尝试编译时,出现以下错误:
-- GraphQL Validation Error -- AddCampaignMutation --
File: /Users/me/docker/relay/examples/todo/js/mutations/AddCampaignMutation.js
Error: Cannot query field "addCampaign" on type "Mutation".
Source:
>
> mutation AddCampaignMutation {addCampaign}
> ^^^
-- GraphQL Validation Error -- AddCampaignMutation --
File: /Users/me/docker/relay/examples/todo/js/mutations/AddCampaignMutation.js
Error: Unknown type "AddCampaignPayload". Did you mean "AddTodoPayload" or "RenameTodoPayload"?
Source:
>
> fragment AddCampaignMutationRelayQL on AddCampaignPayload @relay(pattern: true) {
> ^^
我复制了 Todo 代码,所以我不知道为什么 Todo 突变工作正常,但我的新 Campaign 测试却没有。
这是我的 database.js 文件,我删除了 Todo 相关项以使文档更易于阅读:
export class Campaign {}
export class User {}
// Mock authenticated ID
const VIEWER_ID = 'me';
// Mock user data
const viewer = new User();
viewer.id = VIEWER_ID;
const usersById = {
[VIEWER_ID]: viewer,
};
// Mock campaign data
const campaignsById = {};
const campaignIdsByUser = {
[VIEWER_ID]: [],
};
let nextCampaignId = 0;
addCampaign('Campaign1');
addCampaign('Campaign2');
addCampaign('Campaign3');
addCampaign('Campaign4');
export function addCampaign(text) {
const campaign = new Campaign();
//campaign.complete = !!complete;
campaign.id = `${nextCampaignId++}`;
campaign.text = text;
campaignsById[campaign.id] = campaign;
campaignIdsByUser[VIEWER_ID].push(campaign.id);
return campaign.id;
}
export function getCampaign(id) {
return campaignsById[id];
}
export function getCampaigns(status = 'any') {
const campaigns = campaignIdsByUser[VIEWER_ID].map(id => campaignsById[id]);
if (status === 'any') {
return campaigns;
}
return campaigns.filter(campaign => campaign.complete === (status === 'completed'));
}
这是我的 schema.js 文件,我再次删除了 Todo 相关项以使文档更易于阅读:
import {
GraphQLBoolean,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';
import {
connectionArgs,
connectionDefinitions,
connectionFromArray,
cursorForObjectInConnection,
fromGlobalId,
globalIdField,
mutationWithClientMutationId,
nodeDefinitions,
toGlobalId,
} from 'graphql-relay';
import {
Campaign,
addCampaign,
getCampaign,
getCampaigns,
User,
getViewer,
} from './database';
const {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
const {type, id} = fromGlobalId(globalId);
if (type === 'User') {
return getUser(id);
} else if (type === 'Campaign') {
return getCampaign(id);
}
return null;
},
(obj) => {
if (obj instanceof User) {
return GraphQLUser;
} else if (obj instanceof Campaign) {
return GraphQLCampaign;
}
return null;
}
);
/**
* Define your own connection types here
*/
const GraphQLAddCampaignMutation = mutationWithClientMutationId({
name: 'AddCampaign',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
campaignEdge: {
type: GraphQLCampaignEdge,
resolve: ({localCampaignId}) => {
const campaign = getCampaign(localCampaignId);
return {
cursor: cursorForObjectInConnection(getCampaigns(), campaign),
node: campaign,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localCampaignId = addCampaign(text);
return {localCampaignId};
},
});
const GraphQLCampaign = new GraphQLObjectType({
name: 'Campaign',
description: 'Campaign integrated in our starter kit',
fields: () => ({
id: globalIdField('Campaign'),
text: {
type: GraphQLString,
description: 'Name of the campaign',
resolve: (obj) => obj.text,
}
}),
interfaces: [nodeInterface]
});
const {
connectionType: CampaignsConnection,
edgeType: GraphQLCampaignEdge,
} = connectionDefinitions({
name: 'Campaign',
nodeType: GraphQLCampaign,
});
const GraphQLUser = new GraphQLObjectType({
name: 'User',
fields: {
id: globalIdField('User'),
campaigns: {
type: CampaignsConnection,
args: {
...connectionArgs,
},
resolve: (obj, {...args}) =>
connectionFromArray(getCampaigns(), args),
},
totalCount: {
type: GraphQLInt,
resolve: () => getTodos().length,
},
completedCount: {
type: GraphQLInt,
resolve: () => getTodos('completed').length,
},
},
interfaces: [nodeInterface],
});
const Root = new GraphQLObjectType({
name: 'Root',
fields: {
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
node: nodeField,
},
});
这是我的 AddCampaignMutation.js 文件:
import Relay from 'react-relay';
export default class AddCampaignMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
console.log('getMutation');
return Relay.QL`mutation{addCampaign}`;
}
getFatQuery() {
console.log('getFatQuery');
return Relay.QL`
fragment on AddCampaignPayload @relay(pattern: true) {
campaignEdge,
viewer {
campaigns,
},
}
`;
}
getConfigs() {
console.log('getConfigs');
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'campaigns',
edgeName: 'campaignEdge',
rangeBehaviors: ({orderby}) => {
if (orderby === 'newest') {
return 'prepend';
} else {
return 'append';
}
},
//rangeBehaviors: ({status}) => {
// if (status === 'completed') {
// return 'ignore';
// } else {
// return 'append';
// }
//},
}];
}
getVariables() {
console.log('getVariables');
return {
text: this.props.text,
};
}
getOptimisticResponse() {
console.log('getOptimisticResponse');
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
campaignEdge: {
node: {
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
最后,这是包含我的文本输入和对 AddCampaignMutation 的调用的应用程序文件:
import AddTodoMutation from '../mutations/AddTodoMutation';
import AddCampaignMutation from '../mutations/AddCampaignMutation';
import TodoListFooter from './TodoListFooter';
import TodoTextInput from './TodoTextInput';
import React from 'react';
import Relay from 'react-relay';
class TodoApp extends React.Component {
_handleTextInputSave = (text) => {
debugger;
this.props.relay.commitUpdate(
new AddTodoMutation({text, viewer: this.props.viewer})
);
};
_campaignHandleTextInputSave = (text) => {
debugger;
this.props.relay.commitUpdate(
new AddCampaignMutation({text, viewer: this.props.viewer})
);
};
render() {
const hasTodos = this.props.viewer.totalCount > 0;
return (
<div>
<section className="todoapp">
<header className="header">
<TodoTextInput
autoFocus={true}
className="new-campaign"
onSave={this._campaignHandleTextInputSave}
placeholder="Campaign name"
/>
<h1>
todos
</h1>
<TodoTextInput
autoFocus={true}
className="new-todo"
onSave={this._handleTextInputSave}
placeholder="What needs to be done?"
/>
</header>
{this.props.children}
{hasTodos &&
<TodoListFooter
todos={this.props.viewer.todos}
viewer={this.props.viewer}
/>
}
</section>
</div>
);
}
}
export default Relay.createContainer(TodoApp, {
fragments: {
viewer: () => Relay.QL`
fragment on User {
totalCount,
${AddTodoMutation.getFragment('viewer')},
${AddCampaignMutation.getFragment('viewer')},
${TodoListFooter.getFragment('viewer')},
}
`,
},
});