A brief overview of the relay store and its updater functions
Tags: relay, webdev, programming, javascript
Published: 2024-01-16 13:17:02

> This Article will give you a brief introduction on how we can update the relay store in real time without waiting for any server response. Before reading it, you should have a brief understanding on how relay works and some javascript knowledge won’t hurt as well. 

When working on a drag and drop implementation for the [Carla](carla.se) Backoffice, we quickly realized, that waiting for a server response is not always good enough when you want to build good and user-friendly software. What if we don’t want to wait for the network request to be finished? We already know what will change so we could update it ourself. This would provide a smoother user experience, by making it appear like the mutation has already taken place, even though the server-side data may not have changed yet. To understand how we can do that with relay, we first need to understand what the relay store is.

As by definition it stores the payload of our GraphQl queries to help reduce the number of network requests. It is automatically updated when a query is made.

To do that relay provides us so called updater functions we can pass to the _UseMutationConfig_. We will look at _optimisticResponse_ and _optimisticUpdater_. With the help of those two we can let the store know what the actual result will look like before it’s there or manipulate the store data directly. No matter what we do in those functions, it will be rolled back or overwritten if the server responds with an error or different data.

In the following we will look at some code examples. I will link the whole project soon but for now you should be good with the snippets I put here. Starting with the parts of the graphQl schema which are interesting for us. 

```rescript
type Query {
  _: Int
  todos(options: PageQueryOptions): TodosPage
  todo(id: ID!): Todo
}

type Mutation {
  _: Int
  updateTodo(id: ID!, input: UpdateTodoInput!): Todo
}

type TodosPage {
  data: [Todo]
  links: PaginationLinks
  meta: PageMetadata
}

type Todo {
  id: ID
  title: String
  completed: Boolean
  user: User
}

input UpdateTodoInput {
  title: String
  completed: Boolean
}

type User {
  id: ID
  name: String
  todos(options: PageQueryOptions): TodosPage
}
```
Yes! It’s going to be another todo app. Nothing fancy we just need some use cases we can look at. The only thing we will do is updating the complete status of one or more todo items.

## Optimistic Response
As mentioned before, in the _optimisticResponse_ function we can let the store know what the result will most likely look like. 

The following code snippet shows the query we use to fetch all todos.

```rescript
const TodoQuery = graphql`
  query AppTodoQuery {
    todos {
      data {
        id
        title
        completed
      }
      meta {
        totalCount
      }
    }
  }
`;

const preloadedQuery = loadQuery(RelayEnvironment, TodoQuery, {
  options: {
    paginate: {
      page: 1,
      limit: 5,
    },
  },
});
```

There is nothing out of the ordinary here. With that said let’s get to the more interesting part. The actual mutation we use to update the todo. 

```rescript
const updateTodoMutation = graphql`
  mutation AppUpdateTodoMutation($id: ID!, $input: UpdateTodoInput!)
  @raw_response_type {
    updateTodo(id: $id, input: $input) {
      id
      title
      completed
    }
  }
`;
```

The first difference to any mutation you might have used before without using optimistic response is the _@raw_response_type directive_. We need that to make it work.

const [commitUpdateTodo, isMutating] = useMutation(updateTodoMutation)

```rescript
<TodoListItem
	key={id}
	completed={completed}
	onChangeCheckbox={() =>
	  commitUpdateTodo({
	    variables: {
	      id: id,
        input: { completed: !completed },
	     },
			optimisticResponse: {
	      updateTodo: {
	        id: id,
					completed: !completed,
					title: title,
			},
		},
	})
}>{title}</TodoListItem>
```

To mimic the response we keep everything the same but negate the value for the completed entry.
And thats as easy as it gets. But there is more! 

For some cases it’s not enough to only touch the data returned by the mutation and we want to do more complicated stuff with, for example, the other todo items. For that we can use the optimistic updater.

## Optimistic Updater
Let us first look at how we can achieve the same we did with the _optimisticResponse_ above. The _optimisticUpdater_ is giving us a [RecordSourceSelectorProxy](https://relay.dev/docs/api-reference/store/#recordsourceselectorproxy) we call selector here. With that interface we can do changes and get data from the relay store. As you can see, we use this to get the todo record we try to change. And again, we don’t actually get the the record itself but rather an interface we can use to manipulate the data. In that case it’s a [RecordProxy](https://relay.dev/docs/api-reference/store/#recordproxy). Thats why we can use the _setValue_ function to negate the completed value. Those interface make sure our data is immutable. 

```rescript
<TodoListItem
	key={id}
	user={user}
	completed={completed}
	onChangeCheckbox={() =>
		commitUpdateTodo({
			variables: {
				id: id,
				input: { completed: !completed },
		},
			optimisticUpdater: (selector) => {
				const todo = selector.get(id);
				todo.setValue(!completed, "completed");
		},             
	})
}>{title}</TodoListItem>
```

Now Imagine for our todo list, we update all the todos created by a user as soon as one of them is completed. Sounds like a weird example but you never know what the stakeholder comes up with. (And there is no other use case I can think of with using the free graphQl api I found). We still only get the record returned we mutated so we cant use _optimisticResponse_. In that case we need to use the _optimisticUpdater_. 

```rescript
<TodoListItem
	key={id}
	user={user}
	completed={completed}
	onChangeCheckbox={() =>
		commitUpdateTodo({
			variables: {
				id: id,
				input: { completed: !completed },
		},
		optimisticUpdater: (selector) => {
			const root = selector.getRoot();
			const todosPage = root.getLinkedRecord("todos");
			const todos = todosPage.getLinkedRecords("data");
			todos.filter((todo) => 
				todo.getLinkedRecord("user").getValue("id") === user.id)
				.map((filteredTodo) =>
					filteredTodo.setValue(!completed, "completed")
			);
		},
                 
	})
}>{title}</TodoListItem>
```

We now need to get the store root so we can get all todos. One thing which helped me knowing what I have and what I need to do is logging the _getType_ function on my _RecordProxies_. With that I can double check with the Schema and be sure I am on the right track of doing what I want to do. Let’s look at the example above. After calling _getRoot_ I get the _RecordProxy_ representing the root of our store. With that I can get the todos interface. 
Looking at the schema I can see that they are of the TodosPage type which looks like this: 

```rescript
type TodosPage {
  data: [Todo]
  links: PaginationLinks
  meta: PageMetadata
}
```

From there we can use _getLinkedRecords_ to get the data and use simple array functions to filter for the same user Id and map to change the value. 

## Conclusion
I hope this helped you to get some understanding on how and when to use relays updater functions. There are some downsides you should be aware of. For the _optimisticResponse_ you need to be careful when having big queries. The _@raw_response_type_ directive can cause performance issues. 
For the _optimisticUpdater_ my biggest concern is the complexity which will be added to the codebase. You would need to learn about the interfaces used to manipulate the store.