Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Apollo's writeFragment to update nested list

I am working on a application in which a ship can be configured using rudders and other stuff. The database structure is sort of nested, and so far I have been keeping my GraphQL queries in correspondence with the database.

That means: I could fetch a ship using some query ship(projectId, shipId), but instead I am using a nested query:

query {
    project(id:1) {
        id
        title
        ship(id:1) {
            id
            name
            rudders {
                id
                position
            }
        }
    }
}

Such a structure of course leads to a lot of nested arrays. For example, if I have just added a new rudder, I would have to retrieve using cache.readQuery, which gives me the project object rather than the rudder list. To add the rudder to the cache, I'd get a long line with nested, destructured objects, making the code hard to read.

So I thought of using GraphQL fragments. On the internet, I see them being used a lot to prevent having to re-type several fields on extensive objects (which I personally find very useful as well!). However, there are not so many examples where a fragment is used for an array.

Fragments for arrays could save all the object destructuring when appending some data to an array that is nested in some cached query. Using Apollo's readFragment and writeFragment, I managed to get something working.

The fragment:

export const FRAGMENT_RUDDER_ARRAY = gql`
    fragment rudderArray on ShipObject {
        rudders {
            id
            position
        }
    }
`

Used in the main ship query:

query {
    project(id: ...) {
        id
        title
        ship(id: ...) {
            id
            name
            ...rudderArray
        }
    }
}
${RUDDER_FRAGMENT_ARRAY}

Using this, I can write a much clearer update() function to update Apollo's cache after a mutation. See below:

const [ createRudder ] = useMutation(CREATE_RUDDER_MUTATION, {
    onError: (error) => { console.log(JSON.stringify(error))},
    update(cache, {data: {createRudder}}) {
        const {rudders} = cache.readFragment({
            id: `ShipObject:${shipId}`,
            fragment: FRAGMENT_RUDDER_ARRAY,
            fragmentName: 'rudderArray'
        });

        cache.writeFragment({
            id: `ShipObject:${shipId}`,
            fragment: FRAGMENT_RUDDER_ARRAY,
            fragmentName: 'rudderArray',
            data: {rudders: rudders.concat(createRudder.rudder)}
        });
    }
});

Now what is my question? Well, since I almost never see fragments being used for this end, I find this working well, but I am wondering if there's any drawbacks to this.

On the other hand, I also decided to share this because I could not find any examples. So if this is a good idea, feel free to use the pattern!

like image 528
ErikRtvl Avatar asked Sep 08 '25 11:09

ErikRtvl


1 Answers

Optimized Update Function

const [createRudder] = useMutation(CREATE_RUDDER_MUTATION, {
update(cache, { data: { createRudder } }) {
const existingData = cache.readFragment({
  id: `ShipObject:${shipId}`,
  fragment: FRAGMENT_RUDDER_ARRAY,
});

if (!existingData) return; // Ensure fragment exists

cache.writeFragment({
  id: `ShipObject:${shipId}`,
  fragment: FRAGMENT_RUDDER_ARRAY,
  data: {
    rudders: [...existingData.rudders, createRudder.rudder], // Append new rudder
  },
});
},
});

*Reasoning behind this step works:

Validates if a fragment exists while making changes(deletes the probability of making mistakes).

Avoids fetching the entire query and instead only retrieves fragments that contain the rudder updates.

Maintains clarity and simplicity in the update function.*

like image 74
Manav Avatar answered Sep 11 '25 02:09

Manav