@inline not working with @catch, nor @throwOnFieldError
Hey
Unfortunately, fragments annotated with @inline don't work in combination with @catch, @catch(to: NULL), nor @throwOnFieldError.
And the scary thing is that the Relay Compiler is not complaining and also the generated typeScript types are generated as we'd expect them to be, but the runtime times don't correspond to the TypeScript types.
In the example, this part of our Schema is used:
type Product implements Node {
id: ID!
name: String @semanticNonNull
nameExtensions: ProductNameExtension @semanticNonNull
}
type ProductNameExtension {
properties: String @semanticNonNull
}
And with this component, different scenarios can be tested:
import { graphql, readInlineData, useFragment } from "@segments/relay";
import type { FunctionComponent } from "react";
import type { componentFour$key } from "./__generated__/componentFour.graphql.ts";
import type { componentFourCatchNullInline$key } from "./__generated__/componentFourCatchNullInline.graphql.ts";
import type { componentFourCatchResultInline$key } from "./__generated__/componentFourCatchResultInline.graphql.ts";
import type { componentFourThrowInline$key } from "./__generated__/componentFourThrowInline.graphql.ts";
interface IComponentFourProps {
productKey: componentFour$key;
}
export const ComponentFour: FunctionComponent<IComponentFourProps> = ({
productKey,
}) => {
const product = useFragment(
graphql`
fragment componentFour on Product {
name
...componentFourThrowInline
...componentFourCatchResultInline
...componentFourCatchNullInline
}
`,
productKey,
);
const onClickThrow = () => {
const productInline = readInlineData(
graphql`
fragment componentFourThrowInline on Product
# 👉 @throwOnFieldError changes TypeScript types for productInline, but not the actual runtime behaviour.
# 👉 TypeScript types of productInline don't correspond to actual runtime types 😱.
@throwOnFieldError
@inline {
nameExtensions {
properties # 👉 In the Response this field has an error
}
}
`,
product as componentFourThrowInline$key,
);
// 👉 This results in a runtime error when trying to read `properties`, instead of throwing when `readInlineData` is called.
console.log("Log Inline Data: @throwOnFieldError", {
nameExtensions: {
properties: productInline.nameExtensions.properties,
},
});
};
const onClickCatchResult = () => {
const productInline = readInlineData(
graphql`
fragment componentFourCatchResultInline on Product
# 👉 @catch changes TypeScript types for productInline, but not the actual runtime behaviour.
# 👉 TypeScript types of productInline don't correspond to actual runtime types 😱.
@catch(to: RESULT)
@inline {
nameExtensions {
properties # 👉 In the Response this field has an error
}
}
`,
product as componentFourCatchResultInline$key,
);
if (!productInline.ok) {
// 👉 .ok will always be falsy, since that property doesn't exist in the runtime.
console.log("Log Inline Data: @catch(to: RESULT)", { productInline });
return;
}
// 👉 This is basically unreachable code
console.log("Log Inline Data: @catch(to: RESULT)", {
nameExtensions: {
properties: productInline.value.nameExtensions.properties,
},
});
};
const onClickCatchNull = () => {
const productInline = readInlineData(
graphql`
fragment componentFourCatchNullInline on Product
# 👉 @catch(to: NULL) changes TypeScript types for productInline, but not the actual runtime behaviour.
# 👉 TypeScript types of productInline don't correspond to actual runtime types 😱.
@catch(to: NULL)
@inline {
nameExtensions {
properties # 👉 In the Response this field has an error
}
}
`,
product as componentFourCatchNullInline$key,
);
// 👉 This results in a runtime error when trying to read `properties`, instead of the `productInline` being nulled.
console.log("Log Inline Data: @catch(to: NULL)", {
nameExtensions: {
properties: productInline?.nameExtensions.properties,
},
});
};
return (
<>
<p>Product Name: {product.name}</p>
<button onClick={onClickThrow}>
Log Inline Data: @throwOnFieldError
</button>
<br />
<button onClick={onClickCatchResult}>
Log Inline Data: @catch(to: RESULT)
</button>
<br />
<button onClick={onClickCatchNull}>
Log Inline Data: @catch(to: NULL)
</button>
</>
);
};
It's quite scary, that the generated type don't match the runtime types.
More context: We're relying pretty heavily on @semanticNonNull, to enable our backends to resolve as much data as possible and let the client handle potential errors / partial data (we don't want basically any null bubbling in the backend).
Since this makes all fields basically nullable, we really need to be able to use @catch and @throwOnFieldError everywhere.