facebook/relay

components with deferred fragment data do not suspend when loadNext is called on a connection

jdk243 opened this issue · 0 comments

We have a table component where fragments for individual cells are deferred with the @defer directive. On the initial load of the table, we see these cells suspend and fall back to their parent suspense boundary's fallback value. However, on calls to the loadNext function returned by usePaginationFragment, we see these cells render and the call to useFragment reads undefined rather than causing the component to suspend. However, using the refetch function returned by usePaginationFragment I do see the cells with the deferred fragment suspend successfully.

Below is a simplified example of the components and fragments I'm working with when experiencing this behavior:

import React from 'react';
import { usePreloadedQuery, graphql } from 'react-relay';

import { EntryPointComponentProps } from 'data-router';
import { Box, Heading, Separator, Table, Button } from 'shared-components';

import { DeferredPaginationPageReferenceMfeQuery } from './__generated__/DeferredPaginationPageReferenceMfeQuery.graphql';
import { PaginationTableReferenceMfe_company$key as CompanyFragmentKey } from './__generated__/PaginationTableReferenceMfe_company.graphql';
import { TableRowReferenceMfe_journey$key as JourneyFragmentKey } from './__generated__/TableRowReferenceMfe_journey.graphql';
import { DeferredTableCellReferenceMfe_journey$key as JourneyFragmentKey } from './__generated__/DeferredTableCellReferenceMfe_journey.graphql';


type Props = EntryPointComponentProps<{
  query: DeferredPaginationPageReferenceMfeQuery;
}>;

const DeferredPaginationPage = ({ queries }: Props) => {
  const data = usePreloadedQuery(
    graphql`
      query DeferredPaginationPageReferenceMfeQuery($companyId: ID!, $first: Int!, $after: String)
      @raw_response_type {
        company: node(id: $companyId) {
          ... on Company {
            ...PaginationTableReferenceMfe_company
            ...RefetchTableReferenceMfe_company
          }
        }
      }
    `,
    queries.query
  );
  if (data.company) {
    return (
      <Box>
        <Heading>Does pagination work with defer?</Heading>
        <Separator css={{ my: '$space6' }} />
        <React.Suspense fallback="Loading...">
          <PaginationTable companyFragmentKey={data.company} />
        </React.Suspense>
      </Box>
    );
  }

  return <div>something is horribly wrong</div>;
};

const PaginationTable = ({
  companyFragmentKey,
}: {
  companyFragmentKey: CompanyFragmentKey;
}) => {
  const { data, loadNext, refetch } = usePaginationFragment(
    graphql`
      fragment PaginationTableReferenceMfe_company on Company
      @refetchable(queryName: "PaginationTableReferenceMfe_company_refetchable") {
        journeys(first: $first, after: $after)
          @connection(key: "PaginationTableReferenceMfe_company_journeys") {
          edges {
            cursor
            node {
              id
              ...TableRowReferenceMfe_journey
            }
          }
          totalCount
        }
      }
    `,
    companyFragmentKey
  );

  return (
    <React.Suspense fallback="Loading...">
      <Table columns={[1, 1, 1]}>
        <Table.Header>
          <Table.HeaderRow>
            <Table.HeaderCell>internal ID</Table.HeaderCell>
            <Table.HeaderCell>name</Table.HeaderCell>
            <Table.HeaderCell>messagesSent</Table.HeaderCell>
          </Table.HeaderRow>
        </Table.Header>
        <Table.Body>
          {data.journeys?.edges.map((j) => (
            <TableRow journeyFragmentKey={j.node} key={j.node.id} />
          ))}
        </Table.Body>
      </Table>
      <Button onClick={() => loadNext(1)}>Load next</Button>
      <Button
        onClick={() =>
          refetch({
            first: 1,
            after: data.journeys?.edges[data.journeys?.edges.length - 1].cursor,
          })
        }
      >
        Refetch
      </Button>
    </React.Suspense>
  );
};

const TableRow = ({ journeyFragmentKey }: { journeyFragmentKey: JourneyFragmentKey }) => {
  const data = useFragment(
    graphql`
      fragment TableRowReferenceMfe_journey on Journey {
        internalId
        name
        ...DeferredTableCellReferenceMfe_journey
          @defer(label: "TableRowReferenceMfe_journey_deferred_cell")
      }
    `,
    journeyFragmentKey
  );

  return (
    <Table.BodyRow>
      <Table.BodyCell>{data.internalId}</Table.BodyCell>
      <Table.BodyCell>{data.name}</Table.BodyCell>
      <React.Suspense fallback="Loading...">
        <DeferredTableCell journeyFragmentKey={data} />
      </React.Suspense>
    </Table.BodyRow>
  );
};

const DeferredTableCell = ({
  journeyFragmentKey,
}: {
  journeyFragmentKey: JourneyFragmentKey;
}) => {
  const data = useFragment(
    graphql`
      fragment DeferredTableCellReferenceMfe_journey on Journey {
        stats {
          messagesSent
        }
      }
    `,
    journeyFragmentKey
  );
  console.log(data);
  return <Table.BodyCell>{data.stats?.messagesSent}</Table.BodyCell>;
};

example responses from queries fired by our calls to load query, load next, and refetch are below

load query:

--END_OF_PART
Content-Type: application/json

{"hasNext":true,"data":{"company":{"__typename":"Company","journeys":{"edges":[{"cursor":"MjAyMy0wOS0wOVQwMzoxODozNy43ODQxOTha","node":{"id":"MDc6Sm91cm5leTE4MDg2","internalId":"18086","name":"Roger Test Stat","__typename":"Journey"}}],"totalCount":5715,"pageInfo":{"endCursor":"MjAyMy0wOS0wOVQwMzoxODozNy43ODQxOTha","hasNextPage":true}},"id":"MDc6Q29tcGFueTU"}},"extensions":{"traceId":"3936810731304619923"}}
--END_OF_PART
Content-Type: application/json

{"hasNext":true,"incremental":[{"data":{"stats":{"messagesSent":31}},"label":"TableRowReferenceMfe_journey$defer$TableRowReferenceMfe_journey_deferred_cell","path":["company","journeys","edges",0,"node"]}]}
--END_OF_PART
Content-Type: application/json

{"hasNext":false}
--END_OF_PART--

load next:

--END_OF_PART
Content-Type: application/json

{"hasNext":true,"data":{"node":{"__typename":"Company","journeys":{"edges":[{"cursor":"MjAyNC0wMi0xMlQxODo0MDozNi41NzE3NDZa","node":{"id":"MDc6Sm91cm5leTMzMTMz","internalId":"33133","name":"Order Shipped 02-12-24 18:40:36","__typename":"Journey"}}],"totalCount":5715,"pageInfo":{"endCursor":"MjAyNC0wMi0xMlQxODo0MDozNi41NzE3NDZa","hasNextPage":true}},"id":"MDc6Q29tcGFueTU"}},"extensions":{"traceId":"3214458784753263795"}}
--END_OF_PART
Content-Type: application/json

{"hasNext":true,"incremental":[{"data":{"stats":{"messagesSent":0}},"label":"TableRowReferenceMfe_journey$defer$TableRowReferenceMfe_journey_deferred_cell","path":["node","journeys","edges",0,"node"]}]}
--END_OF_PART
Content-Type: application/json

{"hasNext":false}
--END_OF_PART--

refetch:

--END_OF_PART
Content-Type: application/json

{"hasNext":true,"data":{"node":{"__typename":"Company","journeys":{"edges":[{"cursor":"MjAyNC0wOC0yNlQyMzoyNzoyMC4wODkwNDVa","node":{"id":"MDc6Sm91cm5leTc3OTI5","internalId":"77929","name":"Some added to cart","__typename":"Journey"}}],"totalCount":5715,"pageInfo":{"endCursor":"MjAyNC0wOC0yNlQyMzoyNzoyMC4wODkwNDVa","hasNextPage":true}},"id":"MDc6Q29tcGFueTU"}},"extensions":{"traceId":"1116799166867392896"}}
--END_OF_PART
Content-Type: application/json

{"hasNext":true,"incremental":[{"data":{"stats":{"messagesSent":0}},"label":"TableRowReferenceMfe_journey$defer$TableRowReferenceMfe_journey_deferred_cell","path":["node","journeys","edges",0,"node"]}]}
--END_OF_PART
Content-Type: application/json

{"hasNext":false}
--END_OF_PART--