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):

src/routes/[[id]]/+page.gql
query Info($id: Int! = 1) {
    species(id: $id) {
        name
        sprites {
            front
        }
    }
}
src/routes/[[id]]/+page.svelte
<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:

src/components/Sprite.svelte
<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>
src/components/Sprite.svelte
<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:

src/routes/[[id]]/+page.gql
query Info($id: Int! = 1) {
    species(id: $id) {
        name
        flavor_text

        ...SpriteInfo
    }
}

And change the instance of Sprite to look like:

src/routes/[[id]]/+page.svelte
<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:

  1. 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.

  2. 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 the fragment 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.

  3. 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 our Sprite 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:

src/components/SpeciesPreview.svelte
<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>
src/components/SpeciesPreview.svelte
<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:

src/routes/[[id]]/+page.gql
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.

src/routes/[[id]]/+page.svelte
<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.