-
-
Save wobsoriano/9a7de2d2aaf9448c2fb952d2746b6907 to your computer and use it in GitHub Desktop.
<script lang="ts"> | |
import { defineComponent, toRaw } from 'vue' | |
import { | |
QueryObserver, | |
type QueryKey, | |
type QueryObserverResult, | |
type QueryClient, | |
} from '@tanstack/query-core' | |
type Todo = { | |
userId: number | |
id: number | |
title: string | |
completed: boolean | |
} | |
export default defineComponent({ | |
inject: ['queryClient'], | |
data: () => ({ | |
todoId: 1, | |
result: {} as QueryObserverResult<Todo, unknown>, | |
observer: null as null | QueryObserver<Todo, unknown, Todo, Todo, QueryKey>, | |
unsubscribe: () => {} | |
}), | |
methods: { | |
async fetchTodo(id: number) { | |
const resp = await fetch('https://jsonplaceholder.typicode.com/todos/' + id) | |
const data = await resp.json() | |
return data | |
}, | |
}, | |
mounted() { | |
this.observer = new QueryObserver<Todo, unknown, Todo, Todo, QueryKey>(this.queryClient as QueryClient, { | |
queryKey: ['todo', 1], | |
queryFn: () => this.fetchTodo(1), | |
}) | |
this.unsubscribe = this.observer.subscribe((result) => { | |
Object.keys(result).forEach((key) => { | |
// @ts-expect-error: Incompatible types | |
this.result[key] = result[key] | |
}) | |
}) | |
}, | |
beforeUnmount() { | |
this.unsubscribe() | |
}, | |
watch: { | |
todoId(id) { | |
toRaw(this.observer)?.setOptions({ | |
queryKey: ['todo', id], | |
queryFn: () => this.fetchTodo(id), | |
}) | |
} | |
} | |
}) | |
</script> | |
<template> | |
<main> | |
<div v-if="result.isLoading">Loading...</div> | |
<div v-else>{{ JSON.stringify(result.data) }}</div> | |
<button @click="todoId++" :disabled="result.isLoading"> | |
{{ result.isLoading ? 'Fetching...' : 'Next todo' }} | |
</button> | |
</main> | |
</template> |
import { createApp } from 'vue' | |
import App from './App.vue' | |
import { QueryClient } from '@tanstack/query-core' | |
const app = createApp(App) | |
const queryClient = new QueryClient() | |
app.provide('queryClient', queryClient) | |
app.mount('#app') |
@Tajcore Looks like a Proxy bug somewhere. You can add toRaw
to the observer
property and the error is gone.
watch: {
todoId(id) {
toRaw(this.observer)?.setOptions({
queryKey: ['todo', id],
queryFn: () => this.fetchTodo(id),
})
}
}
Oh word I'm actually writing this using a class based approach using vue-facing-decorator
that doesn't support the dynamic nature of updating the queryKey sadly. would you update the gist though just asking?
Thanks seeing it now!
Hey @wobsoriano, how would you handle mutations? with just query-core
@Tajcore It is very late, but I found out that tanstack/vue-query integrates just fine in Options API. I am using [email protected]
.
Follow the installation instruction. Add these to the entry point.
app.createApp({});
import UserApp from './user/UserApp.vue';
app.component('UserApp', UserApp);
// ...
+ import { VueQueryPlugin } from '@tanstack/vue-query'
+ app.use(VueQueryPlugin)
app.mount('#app');
Note that my setup is a little unconventional because my index.html entry point is like this. For context, my tech stack is Laravel + Vue. The important part is UserApp
component.
<div id="app">
<user-app :constant="{{ json_encode(array_merge($constant)) }}" />
</div>
In UserApp
, provide the queryClient
. I tried to use useQueryClient
directly in data
, but it throws an error. Something about the hook needs to be called in a setup()
environment or has a provide context.
<script>
import { useQueryClient } from '@tanstack/vue-query';
export default {
provide(){
return {
queryClient: useQueryClient(),
}
},
// ...
}
</script>
<template>
<!-- Content -->
</template>
If the project uses mixins, placing the inject
in the mixins configuration might provide some shortcuts in further development.
export default {
inject: ['queryClient'],
// ...
}
Alternatively, you can just add a single setup()
function in Options API. However, you need to do this in every component that needs access to query client.
<script>
// Alternative method, implicitly use useQueryClient only when needed.
import { useQueryClient } from '@tanstack/vue-query';
export default {
setup(){
return {
queryClient: useQueryClient(),
};
}
// ...
}
</script>
<template>
<!-- Content -->
</template>
Now, for useQuery
. It is possible to just use it directly in data
. It will still be reactive, though with some caviat. A reactive queryKey must be wrapped in reactive
or ref
.
<script>
// Alternative method, implicitly use useQueryClient only when needed.
import { useQueryClient } from '@tanstack/vue-query';
export default {
data(){
const filter = reactive({
search: '',
});
return {
filter,
query: useQuery({
queryKey: ['items', filter]
}),
};
}
}
</script>
<template>
<!-- Content -->
</template>
Any changes to filter will be reactive. The query will refetch
every time filter is modified.
Mutations are similar.
<script>
// Alternative method, implicitly use useQueryClient only when needed.
import { useQueryClient } from '@tanstack/vue-query';
export default {
data(){
const filter = reactive({
search: '',
});
return {
filter,
query: useQuery({
queryKey: ['items', filter],
queryFn: () => { /* ... */ },
}),
mutate: useMutation({
mutationFn: this.submit,
onSuccess: this.success,
}),
};
},
methods: {
submit(){
// ...
},
success(){
// ...
},
}
}
</script>
<template>
<!-- Content -->
</template>
I created an example CodeSandbox . Currently, I am adding another example inspired by TkDodo.
getting
Uncaught (in promise) TypeError: Cannot read from private field
fromthis.observer.setOptions