Fragments
Fragments let each component declare exactly the data it needs, without coupling it to the query that fetches it. The parent query spreads the fragment; the component reads it back through useFragment.
Basic Usage
Define a fragment inside the component that uses it:
import { graphql, useFragment } from '$houdini'
export function ShowCard({ show }) { const data = useFragment(show, graphql(` fragment ShowCardInfo on Show { title posterUrl } `))
return ( <div> <img src={data.posterUrl} alt={data.title} /> <h2>{data.title}</h2> </div> )}The parent query spreads the fragment and passes the result down:
query ShowList { shows { ...ShowCardInfo }}import { ShowCard } from '../../lib/ShowCard'
export default function ShowsPage({ ShowList }) { return ( <ul> {ShowList.shows.map((show) => ( <ShowCard key={show.id} show={show} /> ))} </ul> )}The show prop passed to ShowCard is an opaque reference; useFragment unwraps it into the typed data the component actually needs.
Fragment Arguments
When a component needs to parameterize its data requirements (for example, requesting a profile picture at a specific size), use @arguments to declare the parameters and @with to pass values when spreading the fragment.
Default values are provided with the default key:
fragment UserAvatar on User @arguments(size: { type: "Int", default: 50 }) { avatarURL(size: $size)}To mark an argument as required, add ! to the type. An error will be thrown at codegen time if no value is provided:
fragment UserAvatar on User @arguments(size: { type: "Int!" }) { avatarURL(size: $size)}Provide values when spreading the fragment using @with:
query AllUsers { users { ...UserAvatar @with(size: 100) }}If you use fragment arguments on a field that is also marked for list operations, you must pass the variable value when performing the operation.
Plural Fragments
A fragment is normally spread on a single record, so useFragment hands back a single object. When the fragment lives on a list field, though, we end up with an array of references and no clean way to read them all at once. We can’t call useFragment inside a .map() (that breaks the rules of hooks), so without any help we’d be stuck threading each item through its own component.
The @plural directive solves this by marking the fragment as list shaped. The reference becomes an array and useFragment returns an array of data, so a component can render the whole list:
import { graphql, useFragment } from '$houdini'
export function ShowList({ shows }) { const data = useFragment(shows, graphql(` fragment ShowListRow on Show @plural { title posterUrl } `))
return ( <ul> {data.map((show) => ( <li key={show.title}>{show.title}</li> ))} </ul> )}The parent spreads the fragment inside the list field and passes the whole list down in one go:
query ShowList { shows { ...ShowListRow }}import { ShowList } from '../../lib/ShowList'
export default function ShowsPage({ ShowList: data }) { return <ShowList shows={data.shows} />}A @plural fragment has to be spread on a list field, since that’s the only place an array of references can come from. Spreading it anywhere else is a codegen error.
Refetchable Fragments
Sometimes a component needs to reload its own data with different arguments. Mark the fragment with @refetchable and read it with useFragmentHandle instead of useFragment. The handle hands back a refetch method alongside the data:
import { graphql, useFragmentHandle } from '$houdini'import type { UserInfo } from '$houdini'
export function UserInfo({ user }: { user: UserInfo }) { const { data, refetch } = useFragmentHandle(user, graphql(` fragment UserInfo on User @refetchable @arguments(filter: { type: "String" }) { friends(filter: $filter) { name } } `))
return ( <> {data?.friends.map((friend) => <div key={friend.name}>{friend.name}</div>)} <button onClick={() => refetch({ filter: 'aki' })}>filter friends</button> </> )}Calling refetch re-runs the fragment against the network with the arguments we hand it, layered over whatever it was last loaded with, so we only pass the values we actually want to change. The record’s id is figured out for us from the data already on screen.
The initial values come from wherever the fragment is spread, the same as any fragment that takes arguments. The parent passes them with @with:
query UserPage { user { ...UserInfo @with(filter: "aki") }}The fragment has to live on a type Houdini can look up on its own, which in practice means one that implements Node or has a custom resolver configured. That’s the same requirement paginated fragments carry: under the hood we embed the fragment in a query keyed by the record’s id and re-run that.
@refetchable can’t be combined with @paginate on the same fragment. A paginated fragment is already refetchable on its own, so the two together is a compile-time error.
Fragment Masking
Fragment masking keeps components properly encapsulated by ensuring they can only access the fields they explicitly declared, not fields pulled in by sibling fragments. For a deeper look at why this matters, see the Fragment Colocation guide from gql.tada.
By default, fields selected by a fragment are not accessible directly on the parent object. You can disable masking globally with defaultFragmentMasking: "disable" in your config, or per-fragment with @mask_disable:
query CurrentUser { me { uuid ...UserProfile @mask_disable ...UserMeta }}With @mask_disable, all fields from UserProfile are accessible directly on me alongside uuid. UserMeta fields remain masked. This does not apply recursively unless inner fragments also carry the directive.
Component Fields
Component fields let components register themselves as fields directly on GraphQL types, eliminating the need for explicit imports and prop threading in the route.
Define the component’s data requirements using the @componentField directive. In TypeScript, use the GraphQL type utility:
import type { GraphQL } from '$houdini'
type Props = { user: GraphQL<`{ ... on User @componentField(field: "Avatar") { avatarURL } }`>}
export default function UserAvatar({ user }: Props) { return <img src={user.avatarURL} />}In JavaScript, call graphql() at the module level with the prop parameter:
import { graphql } from '$houdini'
graphql(`{ ... on User @componentField(field: "Avatar", prop: "user") { avatarURL }}`)
export default function UserAvatar({ user }) { return <img src={user.avatarURL} />}The registered field can then be used directly in queries and rendered as a component, with no imports needed in the route:
query Profile { currentUser { Avatar }}export default function ProfilePage({ Profile }: PageProps) { return <Profile.currentUser.Avatar />}Arguments
Combine @componentField with @arguments to accept field arguments:
type Props = { user: GraphQL<`{ ... on User @componentField(field: "Avatar") @arguments(size: { type: "Int" }) { avatarURL(size: $size) } }`>}query Profile { currentUser { Avatar(size: 150) }}Named Exports
For named exports rather than default exports, add the export parameter:
type Props = { user: GraphQL<`{ ... on User @componentField(field: "Avatar", export: "UserAvatar") { avatarURL } }`>}
export function UserAvatar({ user }: Props) { return <img src={user.avatarURL} />}