# Data mutations

Using our [Create, Update, and Delete](https://dataclient.io/vue/concepts/atomic-mutations.md) endpoints with
[Controller.fetch()](https://dataclient.io/vue/api/Controller.md#fetch) reactively updates _all_ appropriate components atomically (at the same time).

[useController()](https://dataclient.io/vue/api/useController.md) gives components access to this global supercharged [setState()](https://react.dev/reference/react/useState#setstate).

[//]: # "TODO: Add create, and delete examples as well (in tabs)"

```ts title="TodoResource"
import { Entity, resource } from '@data-client/rest';

export class Todo extends Entity {
  id = 0;
  userId = 0;
  title = '';
  completed = false;

  static key = 'Todo';
}
export const TodoResource = resource({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos/:id',
  searchParams: {} as { userId?: string | number } | undefined,
  schema: Todo,
  optimistic: true,
});
```

```html title="TodoItem.vue" {8-12,14-16}
<script setup lang="ts">
  import { useController } from '@data-client/vue';
  import { TodoResource, type Todo } from './TodoResource';

  const props = defineProps<{ todo: Todo }>();
  const ctrl = useController();

  const handleChange = (e: Event) =>
    ctrl.fetch(
      TodoResource.partialUpdate,
      { id: props.todo.id },
      { completed: (e.target as HTMLInputElement).checked },
    );
  const handleDelete = () =>
    ctrl.fetch(TodoResource.delete, {
      id: props.todo.id,
    });
</script>

<template>
  <div class="listItem nogap">
    <label>
      <input
        type="checkbox"
        :checked="todo.completed"
        @change="handleChange"
      />
      <s v-if="todo.completed">{{ todo.title }}</s>
      <template v-else>{{ todo.title }}</template>
    </label>
    <CancelButton @click="handleDelete" />
  </div>
</template>
```

```html title="CreateTodo.vue" {10-13}
<script setup lang="ts">
  import { useController } from '@data-client/vue';
  import { TodoResource } from './TodoResource';

  const props = defineProps<{ userId: number }>();
  const ctrl = useController();

  const handleKeyDown = async (e: KeyboardEvent) => {
    if (e.key === 'Enter') {
      ctrl.fetch(TodoResource.getList.push, {
        userId: props.userId,
        title: (e.target as HTMLInputElement).value,
      });
      (e.target as HTMLInputElement).value = '';
    }
  };
</script>

<template>
  <div class="listItem nogap">
    <label>
      <input type="checkbox" name="new" :checked="false" disabled />
      <TextInput size="small" @keydown="handleKeyDown" />
    </label>
    <CancelButton />
  </div>
</template>
```

```html title="TodoList.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { TodoResource } from './TodoResource';
  import TodoItem from './TodoItem.vue';
  import CreateTodo from './CreateTodo.vue';

  const userId = 1;
  const todos = await useSuspense(TodoResource.getList, { userId });
</script>

<template>
  <div>
    <TodoItem v-for="todo in todos" :key="todo.pk()" :todo="todo" />
    <CreateTodo :userId="userId" />
  </div>
</template>
```

Rather than triggering invalidation cascades or using manually written update functions,
Data Client reactively updates appropriate components using the fetch response.

## Optimistic mutations based on previous state {#optimistic-updates}

```ts title="Post"
import { Entity, EntityMixin } from '@data-client/rest';

export class Post extends Entity {
  id = 0;
  author = { id: 0 };
  title = '';
  body = '';
  votes = 0;

  static key = 'Post';

  static schema = {
    author: EntityMixin(
      class User {
        id = 0;
      },
    ),
  };

  get img() {
    return `//loremflickr.com/96/72/kitten,cat?lock=${this.id % 16}`;
  }
}
```

```ts title="PostResource" {15-22}
import { resource } from '@data-client/rest';
import { Post } from './Post';

export { Post };

export const PostResource = resource({
  path: '/posts/:id',
  searchParams: {} as { userId?: string | number } | undefined,
  schema: Post,
}).extend('vote', {
  path: '/posts/:id/vote',
  method: 'POST',
  body: undefined,
  schema: Post,
  getOptimisticResponse(snapshot, { id }) {
    const post = snapshot.get(Post, { id });
    if (!post) throw snapshot.abort;
    return {
      id,
      votes: post.votes + 1,
    };
  },
});
```

```html title="PostItem.vue" {9}
<script setup lang="ts">
  import { useController } from '@data-client/vue';
  import { PostResource, type Post } from './PostResource';

  const props = defineProps<{ post: Post }>();
  const ctrl = useController();

  const handleVote = () => {
    ctrl.fetch(PostResource.vote, { id: props.post.id });
  };
</script>

<template>
  <div>
    <div class="voteBlock">
      <small class="vote">
        <button class="up" @click="handleVote">&nbsp;</button>
        {{ post.votes }}
      </small>
      <img :src="post.img" width="70" height="52" />
    </div>
    <div>
      <h4>{{ post.title }}</h4>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
```

```html title="TotalVotes.vue" {13}
<script setup lang="ts">
  import { Query } from '@data-client/rest';
  import { useQuery } from '@data-client/vue';
  import { PostResource } from './PostResource';

  const queryTotalVotes = new Query(
    PostResource.getList.schema,
    posts => posts.reduce((total, post) => total + post.votes, 0),
  );

  const props = defineProps<{ userId: number }>();
  const totalVotes = useQuery(queryTotalVotes, () => ({ userId: props.userId }));
</script>

<template>
  <div style="text-align: center">
    <small>{{ totalVotes }} votes total</small>
  </div>
</template>
```

```html title="PostList.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { PostResource } from './PostResource';
  import PostItem from './PostItem.vue';
  import TotalVotes from './TotalVotes.vue';

  const userId = 2;
  const posts = await useSuspense(PostResource.getList, { userId });
</script>

<template>
  <div>
    <PostItem v-for="post in posts" :key="post.pk()" :post="post" />
    <TotalVotes :userId="userId" />
  </div>
</template>
```

[getOptimisticResponse](https://dataclient.io/rest/guides/optimistic-updates.md) is just like [setState with an updater function](https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state). [Snapshot](https://dataclient.io/vue/api/Snapshot.md) provides typesafe access to the previous store value,
which we use to return the _expected_ fetch response.

Reactive Data Client ensures [data integrity against any possible networking failure or race condition](https://dataclient.io/rest/guides/optimistic-updates.md#optimistic-transforms), so don't
worry about network failures, multiple mutation calls editing the same data, or other common
problems in asynchronous programming.

## Tracking mutation loading

[useLoading()](https://dataclient.io/vue/api/useLoading.md) enhances async functions by tracking their loading and error states.

```ts title="PostResource"
import { Entity, resource } from '@data-client/rest';

export class Post extends Entity {
  id = 0;
  author = 0;
  title = '';
  body = '';
  votes = 0;

  static key = 'Post';

  get img() {
    return `//loremflickr.com/96/72/kitten,cat?lock=${this.id % 16}`;
  }
}
export const PostResource = resource({
  path: '/posts/:id',
  schema: Post,
});
```

```html title="PostDetail.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { PostResource } from './PostResource';

  const props = defineProps<{ id: number }>();
  const post = await useSuspense(PostResource.get, () => ({ id: props.id }));
</script>

<template>
  <div>
    <div class="voteBlock">
      <img :src="post.img" width="70" height="52" />
    </div>
    <div>
      <h4>{{ post.title }}</h4>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
```

```html title="PostForm.vue"
<script setup lang="ts">
  defineProps<{ loading: boolean; error: Error | undefined }>();
  const emit = defineEmits<{ submit: [data: FormData] }>();

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    const data = new FormData(e.target as HTMLFormElement);
    emit('submit', data);
  };
</script>

<template>
  <form @submit="handleSubmit">
    <TextInput
      label="Title"
      name="title"
      defaultValue="My New Post"
      required
    />
    <TextArea name="body" :rows="12" label="Body" required>
      After clicking 'save', the button will be disabled until the POST
      is completed. Upon completion the newly created post is displayed
      immediately as Reactive Data Client is able to use the fetch
      response to populate the store.
    </TextArea>
    <div v-if="error" class="alert alert--danger">{{ error.message }}</div>
    <div>
      <button type="submit" :disabled="loading">
        {{ loading ? 'saving...' : 'Save' }}
      </button>
    </div>
  </form>
</template>
```

```html title="PostCreate.vue" {9-14}
<script setup lang="ts">
  import { useLoading, useController } from '@data-client/vue';
  import { PostResource } from './PostResource';
  import PostForm from './PostForm.vue';

  const emit = defineEmits<{ navigateToPost: [id: number] }>();
  const ctrl = useController();

  const [handleSubmit, loading, error] = useLoading(
    async (data: FormData) => {
      const post = await ctrl.fetch(PostResource.getList.push, data);
      emit('navigateToPost', post.id);
    },
  );
</script>

<template>
  <PostForm @submit="handleSubmit" :loading="loading" :error="error" />
</template>
```

```html title="Navigation.vue"
<script setup lang="ts">
  import { ref } from 'vue';
  import PostCreate from './PostCreate.vue';
  import PostDetail from './PostDetail.vue';

  const id = ref<undefined | number>(undefined);
</script>

<template>
  <div v-if="id">
    <PostDetail :id="id" />
    <div style="text-align: center">
      <button @click="id = undefined">New Post</button>
    </div>
  </div>
  <PostCreate v-else @navigateToPost="id = $event" />
</template>
```
