Reusing Parts of a Query
As you’ve seen, we can get pretty far by just using GraphQL queries to define our route’s data requirements. However, as our application grows these would quickly become unmanageable. To illustrate this point, look at how we used the Sprite component earlier (copied below without the unrelated bits):
query Info($id: Int! = 1) { species(id: $id) { name sprites { front } }}<Sprite id="species-sprite" src={$Info.data.species.sprites.front} speciesName={$Info.data.species.name}/>At first it might not be clear what the problem is. Sprite defines some props so we had to look up the necessary information in order to give those props their value. However, for the sake of argument, imagine that Sprite is used in many different views across our application. Every place where it shows up, we will have to remember to ask for these two pieces of data so that we can provide the props with the correct value. On top of that, as Sprite evolves to do more, we will have to make sure that everywhere we use it also asks for the new data. Our route is now tightly coupled to Sprite’s implementation.
Wouldn’t it be nice if we had some way of defining these requirements inside of Sprite so we didn’t have to worry about the exact details and could ensure that the query included whatever information Sprite needs? Well, that’s where GraphQL fragments come to the rescue.
GraphQL: What Are Fragments?
It’s safe to skip this section if you familiar with GraphQL Fragments.
Fragments are a super powerful tool in GraphQL that is commonly overlooked. In short, they allow us to describe a selection of fields of a given type without having a concrete instance of that type. They look like this:
fragment MyFragment on Species { name flavor_text}This fragment acts as a reusable bit of query so anywhere we have a field in our document that returns a Species we can ask for this data by appending ... to the fragment name in a selection:
→
Using Fragments
Defining a fragment inside of your component looks a lot like the query from before. Let’s see this in action by updating the Sprite component to look like this:
<script lang="ts"> import { fragment, graphql } from '$houdini' import type { SpriteInfo } from '$houdini'
export let species: SpriteInfo
// prettier-ignore $: info = fragment(species, graphql(` fragment SpriteInfo on Species { name sprites { front } } `))</script>
<div id={$$props.id} class="sprite"> <img src={$info.sprites.front} alt={`${$info.name} sprite`} height="100%" /></div><script> import { fragment, graphql } from '$houdini'
export let species
// prettier-ignore $: info = fragment(species, graphql(` fragment SpriteInfo on Species { name sprites { front } } `))</script>
<div id={$$props.id} class="sprite"> <img src={$info.sprites.front} alt={`${$info.name} sprite`} height="100%" /></div>Next we have to go back to the route and put this fragment to use. Update the query inside of src/routes/[[id]]/+page.gql to look like:
query Info($id: Int! = 1) { species(id: $id) { name flavor_text
...SpriteInfo }}And change the instance of Sprite to look like:
<Sprite id="species-sprite" species={$Info.data.species} />Once that’s all done, there shouldn’t be any noticeable change in your browser.
What Just Happened?
That was pretty quick so let’s review what we just did:
-
We defined a new fragment in the
Spritecomponent which ensured that its parent component always delivered the two pieces of information it needs: the front image source and the name of the species. -
Instead of asking for those bits of data directly as two individual props, the component now has a single prop,
species, which we pass to thefragmentfunction we imported from houdini in order to get the data we need. Notice we don’t use this prop for anything in our component except to pass it into houdini, this ensures we are only using data we asked for in our fragment. -
We then updated the route’s query to use the fragment we defined in the component and passed the
$Info.data.speciesreference we got from the query into ourSpritecomponent as the new prop.
Notice how our route is no longer tightly coupled to any of Sprite’s implementation? All that the route knows is that Sprite needs a Species, so it just had to connect the dots and let Sprite take care of the rest.
Houdini enables us to use fragments as a way to colocate our component’s data requirements next to the actual logic that relies on the fields. This not only saves us the extra typing every time we render a Sprite but it also lets us grow this component without worrying about updating every instance of it.
Composing Fragments
It’s worth mentioning explicitly that you are free to mix and match fragments how ever you want. Fragments can have fragments inside of them and the same fragment can show up multiple times in a single component. To illustrate this, let’s add a section in our Pokédex that shows the different evolved forms of the species we’re looking at.
Before we add anything to our route, let’s update the component defined in src/components/SpeciesPreview to use the new fragment we just added to Sprite. You might want to give it a try without looking ahead but either way, here’s what it should look like now:
<script lang="ts"> import { graphql, fragment } from '$houdini' import { Sprite, Display } from '.' import Number from './SpeciesPreviewNumber.svelte' import type { SpeciesPreview } from '$houdini'
export let species: SpeciesPreview export let number: number
// prettier-ignore $: preview = fragment(species, graphql(` fragment SpeciesPreview on Species { name id
...SpriteInfo } `))</script>
<a href={$preview.id}> <Number value={number} /> <Sprite species={$preview} /> <Display> {$preview.name} </Display></a><script> import { graphql, fragment } from '$houdini' import { Sprite, Display } from '.' import Number from './SpeciesPreviewNumber.svelte'
export let species export let number
// prettier-ignore $: preview = fragment(species, graphql(` fragment SpeciesPreview on Species { name id
...SpriteInfo } `))</script>
<a href={$preview.id}> <Number value={number} /> <Sprite species={$preview} /> <Display> {$preview.name} </Display></a>Once that’s done, go back to the route we’ve been working with and update the query to look like this:
query Info($id: Int! = 1) { species(id: $id) { name flavor_text evolution_chain { ...SpeciesPreview } ...SpriteInfo }}Next, copy and paste the following code above the nav in the right panel. You’ll also want to add imports for SpeciesPreview and SpeciesPreviewPlaceholder from the component directory.
<div id="species-evolution-chain"> {#each $Info.data.species.evolution_chain as form, i} <SpeciesPreview species={form} number={i + 1} /> {/each} <!-- if there are less than three species in the chain, leave a placeholder behind --> {#each Array.from({ length: 3 - $Info.data.species.evolution_chain?.length }) as _, i} <SpeciesPreviewPlaceholder number={$Info.data.species.evolution_chain.length + i + 1} /> {/each}</div>You should now go and confirm that you can see all of the forms associated with a species’ evolution chain. Pretty cool, huh?
Fragments are your building blocks
This point is kind of hard to get across but maybe you can already see what we mean. In this last example we built a component that had needed a particular selection of data and also used another component which itself had its own requirements. By giving each component their own fragment, we were able to maintain a very loose coupling between our components and not have to leave it up to coincidence that the correct information was part of the route’s query.
You should think of fragments as the main building block for describing your component’s data requirements in Houdini. As your project grows, your component library will be filled with components that are smart enough to ask for exactly the information they need. Can you imagine how this could apply to a UserAvatar component that seems to constantly evolve from needing just first and last initials, to also wanting the user’s email, and finally some user-configured favorite color?
What’s Next?
This is just the tip of the iceberg when it comes to fragments but hopefully you can appreciate just how big of an upgrade they are to your component library. It’s time to show off a few of Houdini’s “advanced” features. We’re going to start by looking at how Houdini keeps our UI up to date as we trigger mutations that update our server state.