relay icon indicating copy to clipboard operation
relay copied to clipboard

@catch doesn't work on fragment root of useRefetchableFragment nor usePaginationFragment

Open christiansany opened this issue 5 months ago • 5 comments

Hey

Unfortunately there are issues with @catch in combination with useRefetchableFragment and usePaginationFragment. On a surface level, there is no issue, until refetch or loadNext is called. When these functions are called, everything goes boom.

Given a component like this here, refetch only works when the id is manually provided. And the loadNext function doesn't work at all.

import { graphql, usePaginationFragment } from "@segments/relay";
import { type FunctionComponent, startTransition } from "react";
import type { brandForumContextCommunityDiscussionsList$key } from "./__generated__/brandForumContextCommunityDiscussionsList.graphql.ts";
import type {
  brandForumContextCommunityDiscussionsListRefetchQuery,
  ProductListSortOrder,
} from "./__generated__/brandForumContextCommunityDiscussionsListRefetchQuery.graphql.ts";
interface IBrandForumContextCommunityDiscussionsListProps {
  brandKey: brandForumContextCommunityDiscussionsList$key;
}

export const BrandForumContextCommunityDiscussionsList: FunctionComponent<
  IBrandForumContextCommunityDiscussionsListProps
> = ({ brandKey }) => {
  const {
    data: brand,
    loadNext,
    refetch,
  } = usePaginationFragment<
    brandForumContextCommunityDiscussionsListRefetchQuery,
    brandForumContextCommunityDiscussionsList$key
  >(
    graphql`
      fragment brandForumContextCommunityDiscussionsList on Brand
      @argumentDefinitions(
        first: { type: "Int", defaultValue: 5 }
        after: { type: "String" }
        order: { type: "ProductListSortOrder", defaultValue: AVAILABILITY }
      )
      @refetchable(
        queryName: "brandForumContextCommunityDiscussionsListRefetchQuery"
      )
      @catch {
        products(first: $first, after: $after, order: $order)
          @connection(key: "brandForumContextCommunityDiscussionsList_products")
          @required(action: THROW) {
          edges @required(action: THROW) {
            node {
              id
              name
            }
          }
        }
      }
    `,
    brandKey,
  );

  if (!brand.ok) {
    throw new Error("Simple here to demo purpose");
  }

  const onOrderToggle = (order: ProductListSortOrder) => {
    startTransition(() => {
      refetch({
        // 👇 When the id is not manually provided, this will result in a runtime error
        id: brand.value.id,
        order,
      });
    });
  };

  return (
    <>
      <button onClick={() => onOrderToggle("HIGHEST_PRICE")}>
        Change sort
      </button>
      <ul>
        {brand.value.products.edges.map((edge) => (
          <li key={edge.node.id}>{edge.node.name}</li>
        ))}
      </ul>
      {/* 👇 When loadNext is called, this will execute the refetch query with id: null */}
      <button onClick={() => loadNext(2)}>Load more</button>
    </>
  );
};

The expectation for this would be that it just works as expected or that the relay compiler tells us that using @catch is not allowed here.

christiansany avatar Sep 09 '25 09:09 christiansany