skip to Main Content

I’m using react, graphql, and apollo. I’ve implemented a server side paginated table. The problem is that when I page forward, and the new items come in from the network request (correctly) they are rendered as appended to the existing items in the table.

The behavior I expect would be that the newly fetched items only are rendered, and then if I page back again the original items should be displayed (from the cache).

Now, reading the apollo docs on this matter I see that this is standard behavior I guess. From the docs: "Once the new data is returned from the server, the updateQuery function is used to merge it with the existing data, which will cause a re-render of your UI component with an expanded list."

But I have not been able to find a way to implement the behavior I want (which is an extremely common pagination pattern)

Here is the code I have:

const [page, setPage] = useState<number>(0);
  const [sortField, setSortField] = useState('name');
  const [sortOrder, setSortOrder] = useState<Order>('ASC');
  const [sortModel, setSortModel] = useState<GridSortModel>([
    { field: 'name', sort: 'asc' },
  ]);

  const { data } = useQuery(Document, {
    variables: {
      id,,
      input: {
        count: 25,
        page: page + 1,
        sort: sortField,
        order: sortOrder,
      },
    },
    notifyOnNetworkStatusChange: true,
    fetchPolicy: 'cache-first',
  });

  const handleSortModelChange = useCallback((model: GridSortModel) => {
    const { field = 'name', sort = 'ASC' } = model[0] || {};
    const order = sort?.toUpperCase() as Order;
    setSortModel(model);
    setSortField(field);
    setSortOrder(order);
  }, []);

  const handlePageChange = useCallback((newPage: number) => {
    setPage(newPage);
  }, []);

I have verified that it is not an issue with the table implementation or other UI code by rendering the raw data and inspecting the results when I paginate

2

Answers


  1. Chosen as BEST ANSWER

    I am not happy with the solution I had to implement and will therefore post a bounty for this when I am able even though I ended up solving this in what I think was a non-optimal way myself. I had to add a custom merge function in the typePolicies for the field on which the pagination was being performed. The code is as follows:

    merge(existing, incoming: MyListType) {
      // If "existing" is present, then perform array merging. Otherwise, return "incoming" as is.
      return existing
        ? {
            ...incoming,
            items: [
              ...existing.items.filter(
                (existingItem) =>
                  !incoming.items.find(
                    (item) => item.id === existingItem.id
                  )
              ),
              ...incoming.items,
            ],
          }
        : incoming;
    },
    

    And in my query:

    fetchPolicy: 'cache-first',

    That said, it seems strange that I needed to make a custom merge function in my typePolicies config in order to get Apollo to handle such a common use case (server pagination without lazy load and server sort). If anyone knows an easier way I'd love to be enlightened in a comment or another solution.


  2. You can use the the fetchMore function is used to fetch additional data without merging it with the existing data. The updateQuery function is set to (prev, { fetchMoreResult }) => fetchMoreResult to replace the existing data with the newly fetched data.

    //modified version of your file
    
    const MyComponent = () => {
      const [page, setPage] = useState<number>(0);
      const [sortField, setSortField] = useState('name');
      const [sortOrder, setSortOrder] = useState<Order>('ASC');
      const [sortModel, setSortModel] = useState<GridSortModel>([
        { field: 'name', sort: 'asc' },
      ]);
    
      const { data, fetchMore } = useQuery(Document, {
        variables: {
          id,
          input: {
            count: 25,
            page: page + 1,
            sort: sortField,
            order: sortOrder,
          },
        },
        notifyOnNetworkStatusChange: true,
        // Use 'cache-and-network' to fetch data from the cache and send a network request
        fetchPolicy: 'cache-and-network', 
      });
    
      const handleSortModelChange = useCallback((model: GridSortModel) => {
        const { field = 'name', sort = 'ASC' } = model[0] || {};
        const order = sort?.toUpperCase() as Order;
        setSortModel(model);
        setSortField(field);
        setSortOrder(order);
      }, []);
    
      const handlePageChange = useCallback((newPage: number) => {
        // Use fetchMore to fetch additional data without merging it with existing data
        fetchMore({
          variables: {
            input: {
              count: 25,
              page: newPage + 1,
              sort: sortField,
              order: sortOrder,
            },
          },
          updateQuery: (prev, { fetchMoreResult }) => fetchMoreResult,
        });
        setPage(newPage);
      }, [fetchMore, sortField, sortOrder]);
    
    
      return (
        // Your JSX code comes here
      );
    };
    
    export default MyComponent;
    

    Let me know if this works.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search