[client-preset] Fragment masking Q&A #8554
Replies: 19 comments 49 replies
-
Answers to the initial discussion with @marco2216 on the v3 roadmap (#8296 (comment)):
There is not way to enable Fragment Masking on specific queries or fragments for now.
I don't see why you would lose type safety here, could you provide an example?
The |
Beta Was this translation helpful? Give feedback.
-
Just wondering - if I have some local (to component) helper functions which intended to work with unmasked fragment data type then I still should pass masked fragment and call const MyComponent = (props: { post: FragmentType<typeof PostFragment> }) => {
const post = useFragment(PostFragment, props.post);
return (
<div>
<h1>{postSummary(post)}</h1>
</div>
);
};
const postSummary = (post : ???TYPE???): string => {
...
} Where Also I can just import this data type from generated Sorry, this concept is pretty new to me. |
Beta Was this translation helpful? Give feedback.
-
Hi @charlypoly. I've got a question re. testing and re-usability. Let's use the example from the blog post to illustrate: import { FragmentType, graphql, useFragment } from './gql'
const Avatar_UserFragment = graphql(/* GraphQL */ `
fragment Avatar_UserFragment on User {
avatarUrl
}
`)
type AvatarProps = {
user: FragmentType<typeof Avatar_UserFragment>
}
export function Avatar(props: AvatarProps) {
const user = useFragment(Avatar_UserFragment, props.user)
return <CircleImage src={user.avatarUrl} />
} Maybe a naive question, but what's the recommended approach for rendering |
Beta Was this translation helpful? Give feedback.
-
One more Q. I am thinking about performance when passing whole object to render just few fields. Something like this: const PostTitleFragment = graphql(`
fragment PostTitleFields on Post {
title
}
`);
export function PostTitle(props: { post: FragmentType<typeof PostTitleFragment> }) {
const { title } = useFragment(PostTitleFragment, props.post);
return <h1>{title}</h1>;
}
export function Post() {
// fetch post ...
return (
<div>
<PostTitle post={post} />
</div>
);
} As I know Relay will re-render |
Beta Was this translation helpful? Give feedback.
-
Hi all, Please know that a new documentation page, with a dedicated section on Fragment Masking, has been released: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#fragment-masking |
Beta Was this translation helpful? Give feedback.
-
As I mentioned at #8603, adding fragment variables would be a big addition. I'm currently unable to use the fragment approach with list components due to this limitation. |
Beta Was this translation helpful? Give feedback.
-
Is there any pattern or recommendation for supporting multiple possible types passed into a component under a single prop? Previously I have used Super contrived example (please don't pay too much attention to the specifics around whether the solution is right for this case): const UserName = graphql(`
fragment UserName on User {
__typename
name
}
`);
const AdminName = graphql(`
fragment AdminName on Admin {
__typename
firstName
lastName
}
`);
// Pre client-preset solution using `ts-pattern`:
export const Name = ({ user: userFragment }: {
user: UserNameFragment | AdminNameFragment;
}) => match(user)
.with({ __typename: 'User' }, (typedUser) => typedUser.name)
.with({ __typename: 'Admin' }, (typedAdmin) => `${admin.firstName} ${admin.lastName}`)
.exhaustive();
// With client-preset:
export const Name = ({ user: userFragment }: {
user: FragmentType<typeof UserNameFragment> | FragmentType<typeof AdminNameFragment>; // should that work?
}) => {
// need some way to determine which fragment type was passed — is that possible right now?
} Other case where this may be useful is where two fragment's fields match (but are not defined as an interface), and we want to be able to pass either of them into a common component. |
Beta Was this translation helpful? Give feedback.
-
I have a question regarding const books = getFragmentData(
Book,
order?.books
); This works, but the returned array is typed as
As a result, I'm forced to use some other solution, like const books = order?.books.map((bookFragment) => getFragmentData(Book, bookFragment)); Which is okay, but it feels like a bit of a wasteful map considering it's only serving to appease TS. So yeah, would love to learn why |
Beta Was this translation helpful? Give feedback.
-
Hi, I would like to know if it is safe to use Are there any caveat with this way to do ? Thank you :) |
Beta Was this translation helpful? Give feedback.
-
I posted a separate question #9748 but figured I'd write here as well. Has anyone else experienced issues with VSCode not picking up fragment definitions from other files? graphql-codegen itself has no problem generating the correct types, but vs code's graphql plugin can't figure it out |
Beta Was this translation helpful? Give feedback.
-
I'm trying to figure it out how to make fragment masking play nice with Jest and Apollo's MockedProvider. I bet fragment masking prevents me from testing my presentational and non presentational components together. |
Beta Was this translation helpful? Give feedback.
-
I am trying to figure out how to approach developing with fragments. Assume that we are building a complex component composed with nested components that need fragments. As far as I can tell, I can't start by building the low-level components first because the query does not exist at that level and codegen will not generate any fragments. I have to start top-down, define the highest level component and the nested component tree down to the low-level component for the fragment to be generated. Is this correct? Is there a better way? |
Beta Was this translation helpful? Give feedback.
-
I am trying to use fragments in my code, but for some reason fragments are not being generated. Here's my highest level component which works perfectly fine. It queries a user object and and displays the user's const UserProfileDocument = graphql(/* GraphQL */ `
query UserProfile($id: ID!) {
user(id: $id) {
id
fullName
}
}
`);
export default function UserProfile() {
const { data } = useQuery(UserProfileDocument, {
variables: {
id: userId,
},
});
const user = data?.user;
if (!user) return undefined;
return (
<div className="mx-auto max-w-3xl p-4">
{user.id} {user.fullName}
<UserProfileHeader />
</div>
);
} Now I want to pull the Here's my import { graphql, useFragment, FragmentType } from '@/generated/gql';
const UserProfileHeaderFragment = graphql(/* GraphQL */ `
fragment UserProfileHeader on User {
id
fullName
}
}
`);
// type UserProfileHeaderProps = {
// user: FragmentType<typeof UserProfileHeaderFragment>;
// };
export function UserProfileHeader() {
return <div>UserProfileHeader</div>;
} |
Beta Was this translation helpful? Give feedback.
-
I have read all the questions here and in the various links you have provided and I am still not sure what masking is for? Let's say I have a fragment:
And a query:
If I run |
Beta Was this translation helpful? Give feedback.
-
Hi folks, I am getting a typescript error with code generated for unions. My union is defined as follows in the schema: type OrderEvent {
id: ID!
orderEventType: OrderEventType!
order: Order!
}
type StatementEvent {
id: ID!
statementEventType: StatementEventType!
orders: [Order!]!
}
union Event = OrderEvent | StatementEvent I am trying to use the import type { FragmentType } from '@/generated/gql';
import { graphql, getFragmentData } from '@/generated/gql';
const OrderAlertItemFragment = graphql(/* GraphQL */ `
# eslint-disable-next-line @graphql-eslint/no-one-place-fragments
fragment OrderAlertItem on Alert {
id
event {
... on OrderEvent {
id
orderEventType
order {
id
}
}
}
}
`);
interface OrderAlertItemProps {
alert: FragmentType<typeof OrderAlertItemFragment>;
}
export function OrderAlertItem(props: OrderAlertItemProps) {
// eslint-disable-next-line react/destructuring-assignment
const alert = getFragmentData(OrderAlertItemFragment, props.alert);
const { event } = alert;
return (
<>
<p className="text-sm font-medium leading-none">{event.orderEventType}</p>
<p className="text-sm text-muted-foreground">{event.order.id}</p>
</>
);
} However, I am getting typescript errors in the two lines with
I don't understand why typescript is looking for property 'orderEventType' on a You can find my full repo here: https://github.com/nareshbhatia/graphql-fragments-example-3 To see the about error, simply build the app as follows: npm ci
npm run build |
Beta Was this translation helpful? Give feedback.
-
Hi folks, I am running into another TypeScript error with union types. Looking for some help to resolve this. My union looks like this: union Event = OrderEvent | StatementEvent Here's my highest level graphql document with two fragments: // ----- AlertView.tsx -----
const alertViewDocument = graphql(/* GraphQL */ `
query alertView($id: ID!) {
alert(id: $id) {
id
event {
... on OrderEvent {
id
}
... on StatementEvent {
id
}
}
...OrderEventView
...StatementEventView
}
}
`);
export function AlertView({ alertId }: AlertViewProps) {
...
const { event } = alert;
return (
<div>
{event.__typename === 'OrderEvent' && <OrderEventView alert={alert} />}
{event.__typename === 'StatementEvent' && <StatementEventView alert={alert} />}
</div>
);
);
} Here's how // ---- OrderEventView.tsx -----
import type { OrderEvent } from '@/generated/gql/graphql';
const OrderEventViewFragment = graphql(/* GraphQL */ `
fragment OrderEventView on Alert {
id
event {
... on OrderEvent {
id
orderEventType
order {
...OrderView
}
}
}
}
`);
interface OrderEventViewProps {
alert: FragmentType<typeof OrderEventViewFragment>;
}
export function OrderEventView({ alert: alertProp }: OrderEventViewProps) {
const alert = getFragmentData(OrderEventViewFragment, alertProp);
const { event } = alert;
const orderEvent = event as OrderEvent;
const { order, orderEventType } = orderEvent;
return (
<div>
<p>{orderEventType}</p>
{/**********************************************************************************
Type 'Order' has no properties in common with type
'{ ' $fragmentRefs'?: { OrderViewFragment: OrderViewFragment; } | undefined; }'
ts(2559)
**********************************************************************************/}
<OrderView order={order} /> <--- TypeScript error here
</div>
);
} For completeness, here's the definition of the const OrderViewFragment = graphql(/* GraphQL */ `
fragment OrderView on Order {
id
account {
id
firstName
lastName
email
}
total
}
`); The full code for the application is here, It's the |
Beta Was this translation helpful? Give feedback.
-
Hi all, I am trying to figure out how to pass an array fragment to a child component. Didn't find any examples for doing this. Hopefully someone here can help. My schema: type Alert {
id: ID!
...
}
type Query {
alerts: [Alert!]!
} My parent component that performs the main query is below. Can't figure out how to pass the array down: const alertsPageDocument = graphql(/* GraphQL */ `
query alertsPage {
alerts {
id
}
// ?????? How to do this ??????
...AlertList
}
`);
export function AlertsPage() {
const { data, loading, error } = useQuery(alertsPageDocument);
const alerts = data?.alerts;
return (
<AlertList alerts={alerts} />
);
} My child component that renders the array is below. Can figure out how to accept the array: const AlertListFragment = graphql(/* GraphQL */ `
fragment AlertList on Alert[] { <---- This does not work
alerts {
id
...
}
}
`);
interface AlertListProps {
alerts: FragmentType<typeof AlertListFragment>;
}
export function AlertList({ alerts: alertsProp }: AlertListProps) {
const alerts = getFragmentData(AlertListFragment, alertsProp);
return (
...
);
} |
Beta Was this translation helpful? Give feedback.
-
I really like Fragment Masking. It feels like it lays down clear rails for component design. However, I've noticed a significant flaw in this mechanism. const UserViewChildFragment = graphql(`
fragment UserViewChild on User {
name
}
`)
const UserViewChild = ({user}) => {
const {name} = useFragment(UserViewChildFragment, props.user)
return <span>{name}</span>
}
// ---
const UserViewParentFragment = graphql(`
fragment UserViewParent on User {
name
icon
...UserViewChild
}
`)
const UserViewParent = ({user}) = {
const {name, icon, ...userViewChild} = useFragment(UserViewParentFragment, user)
return <>
<span>{name}</span>
<img src={icon} />
<UserViewChild user={userViewChild} />
<>
} Code like the one above passes TypeScript checks, but in reality, the name property is extracted from In the current mechanism, passing fragments to child components requires using useFragment, which seems to cause this breakdown. To handle fragments safely, shouldn't there be a type that enforces passing them to child components without using useFragment? |
Beta Was this translation helpful? Give feedback.
-
I'm still pretty lost with the testing approach for fragments with fragment masking, even after reading the docs:
In the end I've ended up with something that doesn't throw typescript errors, and is fairly straightforward to use... but isn't officially recommended and little type safety. Getting here took me hours of frustration and feeling lost. I wish it was clearer. |
Beta Was this translation helpful? Give feedback.
-
Let's discuss all questions related to the
client-preset
's Fragment Masking.Beta Was this translation helpful? Give feedback.
All reactions