I am creating an app that allows users to create lists (all data is stored on mongoDB and accessed via Apollo GraphQL); each list has a property, listItems, an array that stores all the items in the list.
Currently I am now able to move the listItems around and have them setState in an array accessed via state.items as shown in this video. The list movement is controlled by components found within react-sortable-hoc library
The issue I am having is taking this state.items array of listItems and using a mutation to have the state.items array replace the backend respective list.listItems array in the database.
I believe I may have not properly setup the typeDefs for input ListItems (I was getting Error: The type of Mutation.editListItems(listItems:) must be Input Type but got: [ListItem]!. so I changed it to input instead of type which may be an issue?)
Lastly I attempt to call the editListItems mutation in the RankList functional component below (contained in RankList.js) 'onChange'. I am not sure where the issue and when moving items, it seems the mutation is not called. Please advise (let me know if I should include more info)!
Overall, I wonder if the following could be an issue:
- Is the typeDef for editListItems properly implemented?
- Is onChange the right thing to use here?
- Am I calling the mutation properly (in RankList component)?
- Am I properly passing state.items in the editListItems mutation (in RankList component)?
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 (resolvers)
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 (List component shown in the video above)
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;