1
votes

Link with sample gif of issue here!

Hello, 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.

When a user goes to the individaul Ranklist.js component page, it loads the list with all the list items accessed via graphQL query.

From here the app utilizes react-sortable-hoc to allow users to move the list items around. While I am able to move the listItems around, as soon as I let go, the page resets the order to match the order of the array stored on mongoDB.

Please advise me as to why this is the case AND how I can properly implement a way to save the order of list with react-sortable-hoc in conjunction with the database via Apollo graphQL.

import React, { useContext, 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,
    },
  });


  const [submitListItem] = useMutation(SUBMIT_LIST_ITEM_MUTATION, {
    update() {
      setListItem("");
      listItemInputRef.current.blur();
    },
    variables: {
      listId,
      body: listItem,
    },
  });

  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),
    }));
  }

  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={listItems}
            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 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

1 Answers

2
votes

Your onSortEnd function calls setState and updates state.items accordingly, but then you don't ever use state.items in your component -- you're using data.getList.listItems directly instead. Since that value doesn't change after it's initially fetched, your list stays the same.

You should use state.items instead. You can then use useEffect to update state.items whenever the value of listItems changes -- for example, initially when the response from the server is returned or in response to a mutation. Something like:

onEffect(() => {
  if (data && data.getList && data.getList.listItems) {
    setState(() => ({ items: data.getList.listItems }))
  }
}, [data])