useController()
Controller provides type-safe methods to access and dispatch actions to the store.
For instance fetch, invalidate, and setResponse
<script setup lang="ts">
import { useController } from '@data-client/vue';
const props = defineProps<{ id: string }>();
const ctrl = useController();
const handleRefresh = async () => {
await ctrl.fetch(MyResource.get, { id: props.id });
};
const handleSuspend = async () => {
await ctrl.invalidate(MyResource.get, { id: props.id });
};
const handleLogout = () => {
ctrl.resetEntireStore();
};
</script>
useController() must be called inside <script setup> (or setup()), and requires the
DataClientPlugin to be installed.
The same Controller is also available in templates and the Options API as $dataClient.
Examples
Form submission
fetch returns the denormalized response, matching useSuspense()'s return type. This allows using Entity methods like pk().
<script setup lang="ts">
import { useController } from '@data-client/vue';
import { useRouter } from 'vue-router';
import { PostResource } from './PostResource';
const ctrl = useController();
const router = useRouter();
const handleSubmit = async (e: Event) => {
e.preventDefault();
const post = await ctrl.fetch(
PostResource.getList.push,
new FormData(e.target as HTMLFormElement),
);
post.title;
post.computedField;
router.push(`/post/${post.pk()}`);
};
</script>
<template>
<form @submit="handleSubmit"><!-- fields --></form>
</template>
Direct entity update
Use set for immediate updates without network requests. Supports functional updates to avoid race conditions.
<script setup lang="ts">
import { useController } from '@data-client/vue';
import { Article } from './Article';
const props = defineProps<{ articleId: string }>();
const ctrl = useController();
const vote = () =>
ctrl.set(Article, { id: props.articleId }, article => ({
...article,
votes: article.votes + 1,
}));
</script>
<template>
<button @click="vote">Vote</button>
</template>
Invalidate after mutation
Force refetch of related data using invalidate or expireAll.
<script setup lang="ts">
import { useController } from '@data-client/vue';
import { UserResource } from './UserResource';
const ctrl = useController();
const handleClear = async () => {
// invalidate() causes suspense; expireAll() refetches silently
ctrl.expireAll(UserResource.get);
ctrl.expireAll(UserResource.getList);
};
</script>
<template>
<button @click="handleClear">Refresh user data</button>
</template>
For better performance and consistency, prefer including side effect updates in mutation responses.
Prefetching
Use fetchIfStale to prefetch without overfetching fresh data.
<script setup lang="ts">
import { useController } from '@data-client/vue';
import { ArticleResource } from './ArticleResource';
const props = defineProps<{ id: string }>();
const ctrl = useController();
const prefetch = () =>
ctrl.fetchIfStale(ArticleResource.get, { id: props.id });
</script>
<template>
<RouterLink :to="`/article/${id}`" @mouseenter="prefetch">
Read more
</RouterLink>
</template>
Websocket updates
Populate cache with external data via set.
import { onMounted, onUnmounted } from 'vue';
import { useController } from '@data-client/vue';
export function useWebsocket(url: string) {
const ctrl = useController();
let ws: WebSocket;
onMounted(() => {
ws = new WebSocket(url);
ws.onmessage = event => {
const { entity, args, data } = JSON.parse(event.data);
ctrl.set(EntityMap[entity], args, data);
};
});
onUnmounted(() => ws?.close());
}
For production use, implement a Manager for data streams rather than component-level lifecycle hooks. Managers handle connection lifecycle globally and work with SSR.