Updating Lists
The @list Directive
Adding and removing records from a list is done by mixing together a few different generated fragments
and directives. In order to tell the compiler which lists are targets for these operations, you have to
mark them with the @list directive and provide a unique name:
query AllItems { items @list(name: "All_Items") { id }}Some things worth mentioning:
- It's recommended to name these lists with a different casing convention than the rest of your application to distinguish the generated fragments from those in your codebase.
- Sometimes the runtime needs a little help to hunt down the list you want to mutate. See
@parentIDbelow.
@parentID
When the same list name appears under multiple parent records (for example, friends @list(name: "User_Friends") loaded for several different users), the runtime can't tell which parent's list a mutation should target. Pass @parentID on the fragment spread to identify the specific parent:
mutation AddFriend($friendID: ID!, $userID: ID!) { addFriend(friendID: $friendID) { friend { ...User_Friends_insert @parentID(value: $userID) } }}The value argument must be the ID of the parent record that owns the list. Without it, Houdini will update every instance of that list in the cache.
@listID and @includeListID
@parentID requires knowing the parent record's ID at mutation time, but that isn't always possible. The parent type may not expose an ID field, or the ID may not appear anywhere in the query document. @includeListID and @listID solve this by letting the cache identify the list for you.
Add @includeListID to the list field alongside @list or @paginate. The cache will stamp an opaque __id value directly onto the returned list, and the generated TypeScript type will include __id: string so you can read it without any casting:
query AllItems { userNodes { items @list(name: "All_Items") @includeListID { id } }}const items = data?.userNodes.itemsconst listId = items?.__id // string | undefined, typed by codegenThen pass that value to @listID on the mutation fragment spread instead of @parentID:
mutation NewItem($input: AddItemInput!, $listId: ID!) { addItem(input: $input) { item { ...All_Items_insert @listID(value: $listId) } }}Unlike @parentID, @listID works even when the parent has no usable ID in the document: the opaque key encodes everything the cache needs to find the right list instance.
Operations
Once a list is tagged, Houdini generates a set of fragments named after the list (All_Items_insert, All_Items_remove, All_Items_toggle, All_Items_upsert, All_Items_update) that you spread into mutation responses to tell the cache what to do. The cache updates immediately on the client without waiting for a refetch.
Inserting a record
With this field tagged, any mutation that returns an Item can be used to insert items in this list:
mutation NewItem($input: AddItemInput!) { addItem(input: $input) { item { ...All_Items_insert } }}By default, records are appended to the end of the list. Use @prepend to insert at the beginning instead:
mutation NewItem($input: AddItemInput!) { addItem(input: $input) { item { ...All_Items_insert @prepend } }}@append is also available when you want to be explicit. Both work with _toggle fragments as well.
Removing a record
Any mutation that returns an Item can also be used to remove an item from the list:
mutation RemoveItem($input: RemoveItemInput!) { removeItem(input: $input) { item { ...All_Items_remove } }}Toggling a record
Any mutation that returns an Item can also be used to toggle an elements membership of the list. If the item is already in the list, it will be removed; otherwise, it will be added.
mutation ToggleItem($input: ToggleItemInput!) { toggleItem(input: $input) { item { ...All_Items_toggle } }}Updating a record
Use _update when you only want to write new field values to an existing cached record without touching list membership. This is useful when an update comes in for a record you know is already in the cache.
mutation UpdateMessage($id: ID!) { updateMessage(id: $id) { ...All_Messages_update }}Upserting a record
Use _upsert when you want to insert a record if it isn't already in the list, or update its data in place if it is. This avoids duplicates when the same update can apply to both new and existing records.
mutation UpsertMessage($input: MessageInput!) { upsertMessage(input: $input) { ...All_Messages_upsert }}Deleting a record
Sometimes it can be tedious to remove a record from every single list that mentions it. For these situations, Houdini provides a directive that can be used to mark a field in the mutation response holding the ID of a record to delete from all lists.
mutation DeleteItem($id: ID!) { deleteItem(id: $id) { itemID @Item_delete }}Targeting all lists
If the same list name appears in multiple places in your cache (across different queries or fragments), you can target all of them at once with @allLists:
mutation RemoveItem($input: RemoveItemInput!) { removeItem(input: $input) { item { ...All_Items_remove @allLists } }}@allLists cannot be combined with @parentID.
Conditionals
Sometimes you only want to add or remove a record from a list when an argument has a particular value.
For example, in a todo list you might not want to add the result to the list if the view is only showing the completed items. To support this, houdini provides the @when and @when_not directives. These filters apply to the values of arguments passed to the field marked with @list.
mutation NewItem($input: AddItemInput!) { addItem(input: $input) { item { ...All_Items_insert @when_not(completed: true) } }}