我试图为我的简单应用程序创建一个加载更多功能。我使用Sequelize和GraphQL从MySQL获取数据。我使用relay-starter-kit作为我的应用程序的脚手架。
database.js
import Sequelize from 'sequelize';
import _ from 'lodash';
import Faker from 'faker';
const Conn = new Sequelize('relay', 'root', '', {
host: 'localhost',
dialect: 'mysql'
});
const Person = Conn.define('person', {
firstName: {
type: Sequelize.STRING,
allowNull: false
},
lastName: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail: true
}
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
}
});
const Post = Conn.define('post', {
title: {
type: Sequelize.STRING,
allowNull: false
},
content: {
type: Sequelize.STRING,
allowNull: false
}
});
// Relationships
Person.hasMany(Post);
Post.belongsTo(Person);
Conn.sync({ force: true }).then(() => {
_.times(10, () => {
return Person.create({
firstName: Faker.name.firstName(),
lastName: Faker.name.lastName(),
email: Faker.internet.email(),
createdAt: Date.now(),
updatedAt: Date.now()
}).then(person => {
return person.createPost({
title: `Sample title by ${person.firstName}`,
content: 'This is a sample article'
});
});
})
});
// Model types
class User { }
// Mock data
var viewer = new User();
viewer.id = '1';
module.exports = {
// Export methods that your schema can use to interact with your database
getUser: (id) => id === viewer.id ? viewer : null,
getViewer: () => viewer,
Conn
};
schema.js
import {
GraphQLBoolean,
GraphQLFloat,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';
import {
connectionArgs,
connectionDefinitions,
connectionFromArray,
connectionFromPromisedArraySlice,
connectionFromPromisedArray,
fromGlobalId,
globalIdField,
mutationWithClientMutationId,
nodeDefinitions,
} from 'graphql-relay';
import {
// Import methods that your schema can use to interact with your database
getUser,
getViewer,
Conn
} from './database';
/**
* We get the node interface and field from the Relay library.
*
* The first method defines the way we resolve an ID to its object.
* The second defines the way we resolve an object to its GraphQL type.
*/
var {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
var {type, id} = fromGlobalId(globalId);
if (type === 'User') {
return getUser(id);
} else {
return null;
}
},
(obj) => {
if (obj instanceof User) {
return userType;
} else {
return null;
}
}
);
/**
* Define your own types here
*/
const Person = new GraphQLObjectType({
name: "Person",
description: "This represents a person",
fields: () => {
return {
id: {
type: GraphQLString,
resolve(person) {
return person.id
}
},
firstName: {
type: GraphQLString,
resolve(person) {
return person.firstName
}
},
lastName: {
type: GraphQLString,
resolve(person) {
return person.lastName
}
},
email: {
type: GraphQLString,
resolve(person) {
return person.email
}
},
posts: {
type: new GraphQLList(Post),
resolve(person) {
return person.getPosts();
}
}
}
}
});
const Post = new GraphQLObjectType({
name: "Post",
description: "This is a post",
fields: () => {
return {
id: {
type: GraphQLString,
resolve(person) {
return person.id;
}
},
title: {
type: GraphQLString,
resolve(post) {
return post.title;
}
},
content: {
type: GraphQLString,
resolve(post) {
return post.content;
}
},
person: {
type: Person,
resolve(post) {
return post.getPerson();
}
}
}
}
})
var userType = new GraphQLObjectType({
name: 'User',
description: 'A person who uses our app',
fields: () => ({
id: globalIdField('User'),
people: {
type: personConnection,
description: 'A collection of persons',
args: connectionArgs,
resolve(_, args, info) {
console.log("==== INFO ====");
console.log(info);
let gas = {};
gas.first= args.first;
gas.after = args.after;
gas.find = args.find;
delete args.first;
delete args.after;
delete args.find;
return connectionFromPromisedArray(Conn.models.person.findAll({ where: args }), gas);
}
},
})
});
var {connectionType: personConnection} =
connectionDefinitions({ name: 'Person', nodeType: Person });
/**
* This is the type that will be the root of our query,
* and the entry point into our schema.
*/
var queryType = new GraphQLObjectType({
name: 'Query',
description: "This is a root query",
fields: () => ({
node: nodeField,
// Add your own root fields here
viewer: {
type: userType,
resolve: () => getViewer(),
}
}),
});
/**
* This is the type that will be the root of our mutations,
* and the entry point into performing writes in our schema.
*/
var mutationType = new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
// Add your own mutations here
})
});
/**
* Finally, we construct our schema (whose starting query type is the query
* type we defined above) and export it.
*/
export var Schema = new GraphQLSchema({
query: queryType,
types: [queryType, userType, Person, Post]
// Uncomment the following after adding some mutation fields:
// mutation: mutationType
});
App.js
import React from 'react';
import Relay from 'react-relay';
import AnimateOnChange from 'react-animate-on-change';
class Detail extends React.Component {
constructor(props) {
super(props);
this.state = { person: [] };
}
componentWillMount() {
this.setState({ person: this.props.person });
}
render() {
return (
<div>
<h2>Selected User's Detail</h2>
<div className="form-group">
<label>User ID:</label>
<p className="form-control">
<AnimateOnChange
baseClassName="animated"
animationClassName="fadeIn"
animate={true}>{this.props.person.id}</AnimateOnChange>
</p>
</div>
<div className="form-group">
<label>First Name</label>
<p className="form-control">
<AnimateOnChange
baseClassName="animated"
animationClassName="fadeIn"
animate={true}>{this.props.person.firstName}</AnimateOnChange>
</p>
</div>
<div className="form-group">
<label>Last Name</label>
<p className="form-control">
<AnimateOnChange
baseClassName="animated"
animationClassName="fadeIn"
animate={true}>{this.props.person.lastName}</AnimateOnChange>
</p>
</div>
<h2>Selected User's Posts</h2>
<table className="table table-hovered">
<thead>
<tr>
<th>No</th>
<th>Post ID</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
{this.props.person.posts.map((post, key) =>
<tr key={post.id} className="animated fadeIn">
<td>{key + 1}.</td>
<td>{post.id} {post[key]}</td>
<td>{post.title}</td>
<td>{post.content}</td>
</tr>
) }
</tbody>
</table>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { index: 0 }
}
_handleClick(key) {
if (this.state.index !== key) {
this.setState({ index: key })
}
}
_loadMore(){
console.log(this.props.viewer.people);
this.props.relay.setVariables({
first: this.props.relay.variables.first,
after: this.props.viewer.people.pageInfo.endCursor
});
}
render() {
console.log(this.props.viewer.people);
return (
<div className="container">
<h2>Users List</h2>
<button onClick={this._loadMore.bind(this)}>Load more</button>
<ul className="list-group">
{this.props.viewer.people.edges.map((p, key) =>
<li className={key !== this.state.index ? "list-group-item" : "list-group-item active"} onClick={this._handleClick.bind(this, key) } key={p.node.id}>{p.node.firstName} {p.node.lastName} (ID: {p.node.id}) </li>
) }
</ul>
<Detail person={this.props.viewer.people.edges[this.state.index].node}/>
</div>
)
}
}
export default Relay.createContainer(App, {
fragments: {
viewer: () => Relay.QL`
fragment on User {
people(first: $first, after: $after){
edges{
node{
id,
firstName,
lastName,
posts{
id,
title,
content
}
}
}
pageInfo{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
},
initialVariables: {
first: 3,
after: null
}
});
AppHomeRoute.js
import Relay from 'react-relay';
export default class extends Relay.Route {
static queries = {
viewer: (Component) => Relay.QL`
query {
viewer {
${Component.getFragment('viewer')}
}
}
`,
};
static routeName = 'AppHomeRoute';
}
这个应用程序很简单。有一个按钮,当点击时,将触发更多的加载&#39;功能。你可以在App.js中看到这个功能。只要函数运行,它就会返回:
Cannot read property 'reduce' of undefined
。
我已经尝试确保查询是正确的,并且我发现了一些有趣且有趣的内容。当我尝试使用GraphiQL进行查询时。
如果我使用片段(与向服务器发出请求时的应用程序相同):
query App_ViewerRelayQL($id_0: ID!) {
node(id: $id_0) {
id
...F0
}
}
fragment F0 on User {
_people35G0DJ: people(after: "YXJyYXljb25uZWN0aW9uOjI=", first: 2) {
edges {
node {
id
firstName
lastName
posts {
id
title
content
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
id
}
结果:
{
"errors": [
{
"message": "Cannot read property 'reduce' of undefined"
}
]
}
如果我不使用片段(原始查询):
query {
viewer {
id
people(after:"YXJyYXljb25uZWN0aW9uOjE=",first:2) {
edges {
node {
id
firstName
lastName
posts {
id
title
content
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
id
}
}
结果:
{
"data": {
"viewer": {
"id": "VXNlcjox",
"people": {
"edges": [
{
"node": {
"id": "3",
"firstName": "Filomena",
"lastName": "Ebert",
"posts": [
{
"id": "3",
"title": "Sample title by Filomena",
"content": "This is a sample article"
}
]
},
"cursor": "YXJyYXljb25uZWN0aW9uOjI="
},
{
"node": {
"id": "4",
"firstName": "Alessandra",
"lastName": "Muller",
"posts": [
{
"id": "4",
"title": "Sample title by Alessandra",
"content": "This is a sample article"
}
]
},
"cursor": "YXJyYXljb25uZWN0aW9uOjM="
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false
}
}
}
}
}
假设问题在于查询,但我不确定。我现在变得有点沮丧。任何帮助将不胜感激。
感谢。
答案 0 :(得分:0)
我会尝试再调试一下_loadMore函数,看看你是否正确设置了这些变量。
_loadMore(){
console.log(this.props.viewer.people);
this.props.relay.setVariables({
first: this.props.relay.variables.first,
after: this.props.viewer.people.pageInfo.endCursor
});
}
整个查询与使用片段之间的唯一区别在于您动态地传递这些查询变量。因此,似乎您的initialVariables
将after
设置为null,这可能导致查询在GraphQL服务器端中断。