Combining Apollo Query Results


#1

The solution to this question is muddled in my head, so if I’m attacking this in entirely the wrong way, please let me know.

The Goal:
An infinite scrolling list with polled updates. (I can’t use subscriptions because the server framework I’m using hasn’t yet implemented it). Essentially the container paginates the past and polls the future with a timestamp.

The Problem:
I’ve tried several implementations, but I’m not sure how to pass the props correctly. Starting with my query:

const query = gql`
  query messages($threadId: Int!, $first: Int!, $after: String, $timestamp: String){
    thread(threadId: $threadId) {
      id, subject
      messages(first: $first, after: $after, timestamp: $timestamp) {
        pageInfo {
          endCursor
          hasNextPage
        }
        edges {
          node {
            id 
            body 
            isSent
            insertedAt
            updatedAt
            user {
              name
              avatar
            }
          }
        }
      }
    }
  }
`;

Paginated queries do not send a timestamp. Polled queries do. The server returns the correct messages in GraphiQL.

The Apollo Store, via Apollo Dev Tools, updates its store correctly. For instance, if the first paginated query returns messages with ids of 1 and 2, and 20 seconds later the polled query returns a new message with id: 3, it’s added. Or if the message from id: 2 is edited, the Apollo store updates it.

The downstream view components receive props from the query. That means I need to handle the query merging manually. That seems like a lot of silly work. But I’m not sure how to keep these queries while passing the updated store props.

I’ve gone through the store and racked my brain out. Any ideas are appreciated.

Here is my config object. loadMoreRows is returned by the infinite scrolling component when it wants more rows and pollForNewMessages is called by the container component on a setInterval with a timestamp based on the container’s local state.

const configObject = {
  options: (props) => {
    const after = props.endCursor || null;
    return {
      variables: { first, after, threadId: props.threadId }
    };
  },
  force: true,
  props: ({ ownProps, data }) => {
    const { loading, fetchMore, thread } = data;
    const { threadId } = ownProps;

    const pollForNewMessages = ({ timestamp }) => {
      return fetchMore({
        variables: {
          first: 1000, after: null, threadId, timestamp
        },
        updateQuery: (previousResult, { fetchMoreResult }) => {
          const newEdges = fetchMoreResult.thread.messages.edges;
          return {
            ...previousResult,
            thread: {
              ...previousResult.thread,
              messages: {
                edges: merge(previousResult.thread.messages.edges, newEdges, 'id'),
                ...previousResult.thread.messages.pageInfo
              }
            }
          };
        }
      });
    };
    const loadMoreRows = () => {
      return fetchMore({
        variables: {
          after: thread.messages.pageInfo.endCursor || null,
        },
        updateQuery: (previousResult, { fetchMoreResult }) => {
          const newEdges = fetchMoreResult.thread.messages.edges;
          const pageInfo = fetchMoreResult.thread.messages.pageInfo;
          return {
            ...previousResult,
            thread: {
              ...previousResult.thread,
              messages: {
                edges: merge(previousResult.thread.messages.edges, newEdges, 'id'),
                pageInfo
              }
            }
          };
        }
      });
    };
    return {
      loading,
      thread,
      loadMoreRows,
      pollForNewMessages
    };
  }
};

function merge(a, b, key) {

  function x(a) {
    a.forEach(function (b) {
      if (!(b[key] in obj)) {
        obj[b[key]] = obj[b[key]] || {};
        array.push(obj[b[key]]);
      }
      Object.keys(b).forEach(function (k) {
        obj[b[key]][k] = b[k];
      });
    });
  }
  var array = [],
    obj = {};

  x(a);
  x(b);
  return array;
}

export default compose(
  graphql(query, configObject)
)(MessagesContainer);

#2

Hey I think you’ll get better results asking about Apollo on stack overflow or Slack! Happy to answer on SO.


#3

I posted on SO and asked on Slack (with #stack-overslow and #general). No answer from anyone. Maybe it’s a bad question? I have no idea anymore.


#4

Still nothing on this subject? Guidance on pagination + polling would be highly appreciated.