如何将具有多个对象的状态数组作为参数传递给graphql突变?

Posted

技术标签:

【中文标题】如何将具有多个对象的状态数组作为参数传递给graphql突变?【英文标题】:How to pass state array with multiple objects as a parameter to graphql mutation? 【发布时间】:2020-08-23 15:13:16 【问题描述】:

我正在创建一个允许用户创建列表的应用程序(所有数据都存储在 mongoDB 上并通过 Apollo GraphQL 访问);每个列表都有一个属性 listItems,它是一个存储列表中所有项目的数组。

目前,我现在能够移动 listItems 并将它们 setState 在通过 state.items 访问的数组中,如 in this video 所示。列表移动由react-sortable-hoc library中的组件控制

我遇到的问题是使用 listItems 的 state.items 数组并使用突变让 state.items 数组替换数据库中后端相应的 list.listItems 数组。

我相信我可能没有正确设置输入 ListItems 的 typeDefs(我得到 错误:Mutation.editListItems(listItems:) 的类型必须是输入类型但得到:[ListItem]!。 所以我把它改成了 input 而不是 type 这可能是个问题?)

最后,我尝试在下面的 RankList 功能组件中调用 editListItems 突变(包含在 RankList.js 中)“onChange”。我不确定问题出在哪里以及在移动项目时,似乎没有调用突变。请告知(让我知道我是否应该包含更多信息)!

总的来说,我想知道以下是否可能是一个问题:

editListItems 的 typeDef 是否正确实现? 在这里使用 onChange 合适吗? 我是否正确调用了突变(在 RankList 组件中)? 我是否在 editListItems 突变中正确传递 state.items(在 RankList 组件中)?

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;

【问题讨论】:

【参考方案1】:

太多的问题......简单地说:

const [state, setState] = useState( items: [] );

不要在钩子中使用setState...这是误导,setState 用于类组件。

使用一些有意义的名称,例如 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 处理程序传递给可排序的:

<SortableList items=itemList onSortEnd=onSortEnd />

Mutation 应该有 update 处理程序(writeQuery - 阅读文档)来强制查询更新/重新渲染。 在这种情况下并不完全需要(数组已经排序为可排序的,我们正在更新本地数组,它将重新渲染)但它应该在这里(突变失败/错误 - 处理来自 useMutationerror)。

类型很好...但是 api 检测到错误的类型 ... 数组中的每个项目都包含 __typename 属性 ...迭代 newListOrder 元素( fe 使用.map()) 并删除__typename。它应该在console.log(newListOrder) 中可见。

在 Playground (/graphiql) 中尝试后端/API ...在编码前端之前尝试使用变量突变。比较网络请求/响应详细信息。

【讨论】:

非常感谢 xadm 的回复。当我尝试创建您所显示的类型时 input ListItemsInput: [ListItem]! 我收到服务器错误。该输入是否应该以不同的方式书写? (我还有很多问题要问你,请耐心等待!) 查看更新,检查传递的变量,网络请求,在 Playground 中尝试突变。 嘿@xadm 看看我最近的问题。我收到未处理的拒绝(错误):网络错误:响应不成功:收到状态代码 400。我还尝试在操场上使用突变,但它不会让我按下播放!有问题。 ***.com/questions/61692553/… 如回答中所述删除__typename playground 问题 - 在查询下定义变量(输入内容)对象?

以上是关于如何将具有多个对象的状态数组作为参数传递给graphql突变?的主要内容,如果未能解决你的问题,请参考以下文章

我可以将数组作为参数传递给 Java 中具有可变参数的方法吗?

将状态作为参数传递给 switch

如何将多个参数传递给 Process?

如何将向量作为参数传递给另一个向量?

当我们将数组作为参数传递给其他函数时,数组的值如何变化? [复制]

如何将数组作为参数传递给另一个脚本?