如何将具有多个对象的状态数组作为参数传递给 graphql 突变?
How to pass state array with multiple objects as a parameter to graphql mutation?
我正在创建一个允许用户创建列表的应用程序(所有数据都存储在 mongoDB 上并通过 Apollo GraphQL 访问);每个列表都有一个属性,listItems,一个存储列表中所有项目的数组。
目前,我现在可以移动 listItems 并在通过 state.items 访问的数组中设置状态,如图 in this video. The list movement is controlled by components found within react-sortable-hoc library
我遇到的问题是采用这个 state.items listItems 数组并使用突变让 state.items 数组替换数据库中后端相应的 list.listItems 数组。
我相信我可能没有正确设置输入 ListItems 的 typeDef(我收到 错误:Mutation.editListItems(listItems:) 的类型必须是输入类型但得到:[ListItem ]!. 所以我将其更改为 input 而不是 type 这可能是个问题?)
最后,我尝试在下面的 RankList 功能组件中调用 editListItems 突变(包含在 RankList.js 中)'onChange'。我不确定问题出在哪里以及在移动项目时,似乎没有调用突变。请告知(如果我应该包含更多信息,请告诉我)!
总体而言,我想知道是否存在以下问题:
- 是否正确实现了 editListItems 的 typeDef?
- onChange 在这里使用正确吗?
- 我是否正确调用了突变(在 RankList 组件中)?
- 我是否在 editListItems 突变(在 RankList 组件中)中正确地传递了 state.items?
typeDefs.js
const { gql } = require("apollo-server");
//schema
module.exports = gql`
type List {
id: ID!
title: String!
createdAt: String!
username: String!
listItems: [ListItem]!
comments: [Comment]!
likes: [Like]!
likeCount: Int!
commentCount: Int!
}
type ListItem {
id: ID!
createdAt: String!
username: String!
body: String!
}
input ListItems {
id: ID!
createdAt: String!
username: String!
body: String!
}
type Comment {
id: ID!
createdAt: String!
username: String!
body: String!
}
type Like {
id: ID!
createdAt: String!
username: String!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: String!
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getLists: [List]
getList(listId: ID!): List
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
createList(title: String!): List!
editListItems(listId: ID!, listItems: ListItems!): List!
deleteList(listId: ID!): String!
createListItem(listId: ID!, body: String!): List!
deleteListItem(listId: ID!, listItemId: ID!): List!
createComment(listId: ID!, body: String!): List!
deleteComment(listId: ID!, commentId: ID!): List!
likeList(listId: ID!): List!
},
type Subscription{
newList: List!
}
`;
list.js(解析器)
async editListItems(_, { listId, listItems }, context) {
console.log("editListItems Mutation activated!");
const user = checkAuth(context);
const list = await List.findById(listId);
if (list) {
if (user.username === list.username) {
list.listItems = listItems;
await list.save();
return list;
} else {
throw new AuthenticationError("Action not allowed");
}
} else {
throw new UserInputError("List not found");
}
},
RankList.js(上面视频中显示的列表组件)
import React, { useContext, useEffect, useRef, useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/react-hooks";
import { Form } from "semantic-ui-react";
import moment from "moment";
import { AuthContext } from "../context/auth";
import { SortableContainer, SortableElement } from "react-sortable-hoc";
import arrayMove from "array-move";
import "../RankList.css";
import { CSSTransitionGroup } from "react-transition-group";
const SortableItem = SortableElement(({ value }) => (
<li className="listLI">{value}</li>
));
const SortableList = SortableContainer(({ items }) => {
return (
<ol className="theList">
<CSSTransitionGroup
transitionName="ranklist"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
>
{items.map((item, index) => (
<SortableItem
key={`item-${item.id}`}
index={index}
value={item.body}
/>
))}
</CSSTransitionGroup>
</ol>
);
});
function RankList(props) {
const listId = props.match.params.listId;
const { user } = useContext(AuthContext);
const listItemInputRef = useRef(null);
const [state, setState] = useState({ items: [] });
const [listItem, setListItem] = useState("");
const { loading, error, data } = useQuery(FETCH_LIST_QUERY, {
variables: {
listId,
},
onError(err) {
console.log(err.graphQLErrors[0].extensions.exception.errors);
// setErrors(err.graphQLErrors[0].extensions.exception.errors);
}
});
useEffect(() => {
if (data && data.getList && data.getList.listItems) {
setState(() => ({ items: data.getList.listItems }));
}
}, [data]);
// const [state, setState] = useState({ items: data.getList.listItems });
const [submitListItem] = useMutation(SUBMIT_LIST_ITEM_MUTATION, {
update() {
setListItem("");
listItemInputRef.current.blur();
},
variables: {
listId,
body: listItem,
},
});
const [editListItems] = useMutation(EDIT_LIST_ITEMS_MUTATION, {
variables: {
listId,
listItems: state.items,
},
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error..</p>;
function deleteListCallback() {
props.history.push("/");
}
function onSortEnd({ oldIndex, newIndex }) {
setState(({ items }) => ({
items: arrayMove(items, oldIndex, newIndex),
}));
//THIS MAY BE WHERE THE ISSUE LIES
editListItems();
}
let listMarkup;
if (!data.getList) {
listMarkup = <p>Loading list...</p>;
} else {
const {
id,
title,
createdAt,
username,
listItems,
comments,
likes,
likeCount,
commentCount,
} = data.getList;
listMarkup = user ? (
<div className="todoListMain">
<div className="rankListMain">
<div className="rankItemInput">
<h3>{title}</h3>
<Form>
<div className="ui action input fluid">
<input
type="text"
placeholder="Choose rank item.."
name="listItem"
value={listItem}
onChange={(event) => setListItem(event.target.value)}
ref={listItemInputRef}
/>
<button
type="submit"
className="ui button teal"
disabled={listItem.trim() === ""}
onClick={submitListItem}
>
Submit
</button>
</div>
</Form>
</div>
<SortableList
items={state.items}
onSortEnd={onSortEnd}
helperClass="helperLI"
/>
</div>
</div>
) : (
<div className="todoListMain">
<div className="rankListMain">
<div className="rankItemInput">
<h3>{props.title}</h3>
</div>
<SortableList
items={listItems}
onSortEnd={onSortEnd}
helperClass="helperLI"
/>
</div>
</div>
);
}
return listMarkup;
}
const EDIT_LIST_ITEMS_MUTATION = gql`
mutation($listId: ID!, $listItems: ListItems!) {
editListItems(listId: $listId, listItems: $listItems) {
id
listItems {
id
body
createdAt
username
}
}
}
`;
const SUBMIT_LIST_ITEM_MUTATION = gql`
mutation($listId: ID!, $body: String!) {
createListItem(listId: $listId, body: $body) {
id
listItems {
id
body
createdAt
username
}
comments {
id
body
createdAt
username
}
commentCount
}
}
`;
const FETCH_LIST_QUERY = gql`
query($listId: ID!) {
getList(listId: $listId) {
id
title
createdAt
username
listItems {
id
createdAt
username
body
}
likeCount
likes {
username
}
commentCount
comments {
id
username
createdAt
body
}
}
}
`;
export default RankList;
问题太多……简而言之:
const [state, setState] = useState({ items: [] });
不要在钩子中使用 setState
......这是误导,setState
用于 class 个组件。
使用一些有意义的名称,f.e。 itemList
:
const [itemList, setItemList] = useState( [] ); // just array
调整查询结果保存:
useEffect(() => {
if (data && data.getList && data.getList.listItems) {
// setState(() => ({ items: data.getList.listItems }));
setItemList( data.getList.listItems );
}
}, [data]);
调整可排序更新处理程序:
const onSortEnd = ({oldIndex, newIndex}) => {
// this.setState(({items}) => ({
// items: arrayMove(items, oldIndex, newIndex),
// }));
const newListOrder = arrayMove( itemList, oldIndex, newIndex);
// console.log("array sorted", newListOrder );
// update order in local state
// kind of optimistic update
setItemList( newListOrder );
// update remote data
editListItems( {
variables: {
listId,
listItems: newListOrder,
}
});
将 onSortEnd
处理程序传递给 sortable:
<SortableList items={itemList} onSortEnd={onSortEnd} />
Mutation 应该有 update
处理程序(writeQuery
- 阅读文档)来强制查询 update/rerendering。 在这种情况下并不完全需要(数组已经排序为可排序,我们正在更新本地数组,它将重新呈现)但它应该在这里(突变 fail/errors - 从 useMutation
处理 error
)。
类型很好......但是api检测到错误类型......数组中的每一项都包含__typename
属性 ... 遍历 newListOrder
个元素(f.e。使用 .map()
)并删除 __typename
。它应该在 console.log(newListOrder)
.
中可见
在 playground (/graphiql
) 中尝试 backend/API ... 在编码前端之前尝试 使用变量 进行突变。比较网络 requests/response 详细信息。
我正在创建一个允许用户创建列表的应用程序(所有数据都存储在 mongoDB 上并通过 Apollo GraphQL 访问);每个列表都有一个属性,listItems,一个存储列表中所有项目的数组。
目前,我现在可以移动 listItems 并在通过 state.items 访问的数组中设置状态,如图 in this video. The list movement is controlled by components found within react-sortable-hoc library
我遇到的问题是采用这个 state.items listItems 数组并使用突变让 state.items 数组替换数据库中后端相应的 list.listItems 数组。
我相信我可能没有正确设置输入 ListItems 的 typeDef(我收到 错误:Mutation.editListItems(listItems:) 的类型必须是输入类型但得到:[ListItem ]!. 所以我将其更改为 input 而不是 type 这可能是个问题?)
最后,我尝试在下面的 RankList 功能组件中调用 editListItems 突变(包含在 RankList.js 中)'onChange'。我不确定问题出在哪里以及在移动项目时,似乎没有调用突变。请告知(如果我应该包含更多信息,请告诉我)!
总体而言,我想知道是否存在以下问题:
- 是否正确实现了 editListItems 的 typeDef?
- onChange 在这里使用正确吗?
- 我是否正确调用了突变(在 RankList 组件中)?
- 我是否在 editListItems 突变(在 RankList 组件中)中正确地传递了 state.items?
typeDefs.js
const { gql } = require("apollo-server");
//schema
module.exports = gql`
type List {
id: ID!
title: String!
createdAt: String!
username: String!
listItems: [ListItem]!
comments: [Comment]!
likes: [Like]!
likeCount: Int!
commentCount: Int!
}
type ListItem {
id: ID!
createdAt: String!
username: String!
body: String!
}
input ListItems {
id: ID!
createdAt: String!
username: String!
body: String!
}
type Comment {
id: ID!
createdAt: String!
username: String!
body: String!
}
type Like {
id: ID!
createdAt: String!
username: String!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: String!
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getLists: [List]
getList(listId: ID!): List
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
createList(title: String!): List!
editListItems(listId: ID!, listItems: ListItems!): List!
deleteList(listId: ID!): String!
createListItem(listId: ID!, body: String!): List!
deleteListItem(listId: ID!, listItemId: ID!): List!
createComment(listId: ID!, body: String!): List!
deleteComment(listId: ID!, commentId: ID!): List!
likeList(listId: ID!): List!
},
type Subscription{
newList: List!
}
`;
list.js(解析器)
async editListItems(_, { listId, listItems }, context) {
console.log("editListItems Mutation activated!");
const user = checkAuth(context);
const list = await List.findById(listId);
if (list) {
if (user.username === list.username) {
list.listItems = listItems;
await list.save();
return list;
} else {
throw new AuthenticationError("Action not allowed");
}
} else {
throw new UserInputError("List not found");
}
},
RankList.js(上面视频中显示的列表组件)
import React, { useContext, useEffect, useRef, useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/react-hooks";
import { Form } from "semantic-ui-react";
import moment from "moment";
import { AuthContext } from "../context/auth";
import { SortableContainer, SortableElement } from "react-sortable-hoc";
import arrayMove from "array-move";
import "../RankList.css";
import { CSSTransitionGroup } from "react-transition-group";
const SortableItem = SortableElement(({ value }) => (
<li className="listLI">{value}</li>
));
const SortableList = SortableContainer(({ items }) => {
return (
<ol className="theList">
<CSSTransitionGroup
transitionName="ranklist"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
>
{items.map((item, index) => (
<SortableItem
key={`item-${item.id}`}
index={index}
value={item.body}
/>
))}
</CSSTransitionGroup>
</ol>
);
});
function RankList(props) {
const listId = props.match.params.listId;
const { user } = useContext(AuthContext);
const listItemInputRef = useRef(null);
const [state, setState] = useState({ items: [] });
const [listItem, setListItem] = useState("");
const { loading, error, data } = useQuery(FETCH_LIST_QUERY, {
variables: {
listId,
},
onError(err) {
console.log(err.graphQLErrors[0].extensions.exception.errors);
// setErrors(err.graphQLErrors[0].extensions.exception.errors);
}
});
useEffect(() => {
if (data && data.getList && data.getList.listItems) {
setState(() => ({ items: data.getList.listItems }));
}
}, [data]);
// const [state, setState] = useState({ items: data.getList.listItems });
const [submitListItem] = useMutation(SUBMIT_LIST_ITEM_MUTATION, {
update() {
setListItem("");
listItemInputRef.current.blur();
},
variables: {
listId,
body: listItem,
},
});
const [editListItems] = useMutation(EDIT_LIST_ITEMS_MUTATION, {
variables: {
listId,
listItems: state.items,
},
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error..</p>;
function deleteListCallback() {
props.history.push("/");
}
function onSortEnd({ oldIndex, newIndex }) {
setState(({ items }) => ({
items: arrayMove(items, oldIndex, newIndex),
}));
//THIS MAY BE WHERE THE ISSUE LIES
editListItems();
}
let listMarkup;
if (!data.getList) {
listMarkup = <p>Loading list...</p>;
} else {
const {
id,
title,
createdAt,
username,
listItems,
comments,
likes,
likeCount,
commentCount,
} = data.getList;
listMarkup = user ? (
<div className="todoListMain">
<div className="rankListMain">
<div className="rankItemInput">
<h3>{title}</h3>
<Form>
<div className="ui action input fluid">
<input
type="text"
placeholder="Choose rank item.."
name="listItem"
value={listItem}
onChange={(event) => setListItem(event.target.value)}
ref={listItemInputRef}
/>
<button
type="submit"
className="ui button teal"
disabled={listItem.trim() === ""}
onClick={submitListItem}
>
Submit
</button>
</div>
</Form>
</div>
<SortableList
items={state.items}
onSortEnd={onSortEnd}
helperClass="helperLI"
/>
</div>
</div>
) : (
<div className="todoListMain">
<div className="rankListMain">
<div className="rankItemInput">
<h3>{props.title}</h3>
</div>
<SortableList
items={listItems}
onSortEnd={onSortEnd}
helperClass="helperLI"
/>
</div>
</div>
);
}
return listMarkup;
}
const EDIT_LIST_ITEMS_MUTATION = gql`
mutation($listId: ID!, $listItems: ListItems!) {
editListItems(listId: $listId, listItems: $listItems) {
id
listItems {
id
body
createdAt
username
}
}
}
`;
const SUBMIT_LIST_ITEM_MUTATION = gql`
mutation($listId: ID!, $body: String!) {
createListItem(listId: $listId, body: $body) {
id
listItems {
id
body
createdAt
username
}
comments {
id
body
createdAt
username
}
commentCount
}
}
`;
const FETCH_LIST_QUERY = gql`
query($listId: ID!) {
getList(listId: $listId) {
id
title
createdAt
username
listItems {
id
createdAt
username
body
}
likeCount
likes {
username
}
commentCount
comments {
id
username
createdAt
body
}
}
}
`;
export default RankList;
问题太多……简而言之:
const [state, setState] = useState({ items: [] });
不要在钩子中使用 setState
......这是误导,setState
用于 class 个组件。
使用一些有意义的名称,f.e。 itemList
:
const [itemList, setItemList] = useState( [] ); // just array
调整查询结果保存:
useEffect(() => {
if (data && data.getList && data.getList.listItems) {
// setState(() => ({ items: data.getList.listItems }));
setItemList( data.getList.listItems );
}
}, [data]);
调整可排序更新处理程序:
const onSortEnd = ({oldIndex, newIndex}) => {
// this.setState(({items}) => ({
// items: arrayMove(items, oldIndex, newIndex),
// }));
const newListOrder = arrayMove( itemList, oldIndex, newIndex);
// console.log("array sorted", newListOrder );
// update order in local state
// kind of optimistic update
setItemList( newListOrder );
// update remote data
editListItems( {
variables: {
listId,
listItems: newListOrder,
}
});
将 onSortEnd
处理程序传递给 sortable:
<SortableList items={itemList} onSortEnd={onSortEnd} />
Mutation 应该有 update
处理程序(writeQuery
- 阅读文档)来强制查询 update/rerendering。 在这种情况下并不完全需要(数组已经排序为可排序,我们正在更新本地数组,它将重新呈现)但它应该在这里(突变 fail/errors - 从 useMutation
处理 error
)。
类型很好......但是api检测到错误类型......数组中的每一项都包含__typename
属性 ... 遍历 newListOrder
个元素(f.e。使用 .map()
)并删除 __typename
。它应该在 console.log(newListOrder)
.
在 playground (/graphiql
) 中尝试 backend/API ... 在编码前端之前尝试 使用变量 进行突变。比较网络 requests/response 详细信息。