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.
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
$: 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'
/* @type { import('$houdini').SpriteInfo } */
export let species
$: 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
Sprite
component 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 thefragment
function 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.species
reference we got from the query into ourSprite
component 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
$: 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'
/* @type { import('$houdini').SpeciesPreview } */
export let species
/* @type { number } */
export let number
$: 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.
<script>
import { SpeciesPreview, SpeciesPreviewPlaceholder } from '~/components'
</script>
<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?
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.