Skip to content

feat: vue-query adapter #4254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codesandbox/ci.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"installCommand": "install:csb",
"sandboxes": ["/examples/react/basic", "/examples/react/basic-typescript", "/examples/solid/basic-typescript"],
"sandboxes": ["/examples/react/basic", "/examples/react/basic-typescript", "/examples/solid/basic-typescript", "/examples/vue/basic"],
"packages": ["packages/**"],
"node": "16"
}
51 changes: 48 additions & 3 deletions docs/adapters/vue-query.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,52 @@
---
title: Vue Query (Coming Soon)
title: Vue Query
---

> ⚠️ This module has not yet been developed. It requires an adapter similar to `react-query` to work. We estimate the amount of code to do this is low-to-moderate, but does require familiarity with the Vue framework. If you would like to contribute this adapter, please open a PR!
The `vue-query` package offers a 1st-class API for using TanStack Query via Vue. However, all of the primitives you receive from these hooks are core APIs that are shared across all of the TanStack Adapters including the Query Client, query results, query subscriptions, etc.

The `@tanstack/vue-query` package offers a 1st-class API for using TanStack Query via Vue. However, all of the primitives you receive from this API are core APIs that are shared across all of the TanStack Adapters including the Query Client, query results, query subscriptions, etc.
## Example

This example very briefly illustrates the 3 core concepts of Vue Query:

- [Queries](guides/queries)
- [Mutations](guides/mutations)
- [Query Invalidation](guides/query-invalidation)

```vue
<script setup>
import { useQueryClient, useQuery, useMutation } from "vue-query";

// Access QueryClient instance
const queryClient = useQueryClient();

// Query
const { isLoading, isError, data, error } = useQuery(["todos"], getTodos);

// Mutation
const mutation = useMutation(postTodo, {
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries(["todos"]);
},
});

function onButtonClick() {
mutation.mutate({
id: Date.now(),
title: "Do Laundry",
});
}
</script>

<template>
<span v-if="isLoading">Loading...</span>
<span v-else-if="isError">Error: {{ error.message }}</span>
<!-- We can assume by this point that `isSuccess === true` -->
<ul v-else>
<li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
</ul>
<button @click="onButtonClick">Add Todo</button>
</template>
```

These three concepts make up most of the core functionality of Vue Query. The next sections of the documentation will go over each of these core concepts in great detail.
4 changes: 2 additions & 2 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@
"to": "adapters/solid-query"
},
{
"label": "Vue Query (Coming Soon)",
"to": "#"
"label": "Vue Query",
"to": "adapters/vue-query"
},
{
"label": "Svelte Query (Coming Soon)",
Expand Down
6 changes: 6 additions & 0 deletions examples/vue/basic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
package-lock.json
6 changes: 6 additions & 0 deletions examples/vue/basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Basic example

To run this example:

- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
12 changes: 12 additions & 0 deletions examples/vue/basic/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Query Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
19 changes: 19 additions & 0 deletions examples/vue/basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@tanstack/query-example-vue-basic",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"build:dev": "vite build -m development",
"serve": "vite preview"
},
"dependencies": {
"vue": "3.2.39",
"@tanstack/vue-query": "^4.9.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "3.1.0",
"typescript": "4.8.4",
"vite": "3.1.4"
}
}
43 changes: 43 additions & 0 deletions examples/vue/basic/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import { defineComponent, ref } from "vue";

import Posts from "./Posts.vue";
import Post from "./Post.vue";

export default defineComponent({
name: "App",
components: { Posts, Post },
setup() {
const visitedPosts = ref(new Set());
const isVisited = (id: number) => visitedPosts.value.has(id);

const postId = ref(-1);
const setPostId = (id: number) => {
visitedPosts.value.add(id);
postId.value = id;
};

return {
isVisited,
postId,
setPostId,
};
},
});
</script>

<template>
<h1>Vue Query - Basic</h1>
<p>
As you visit the posts below, you will notice them in a loading state the
first time you load them. However, after you return to this list and click
on any posts you have already visited again, you will see them load
instantly and background refresh right before your eyes!
<strong>
(You may need to throttle your network speed to simulate longer loading
sequences)
</strong>
</p>
<Post v-if="postId > -1" :postId="postId" @setPostId="setPostId" />
<Posts v-else :isVisited="isVisited" @setPostId="setPostId" />
</template>
51 changes: 51 additions & 0 deletions examples/vue/basic/src/Post.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script lang="ts">
import { defineComponent } from "vue";
import { useQuery } from "@tanstack/vue-query";

import { Post } from "./types";

const fetcher = async (id: number): Promise<Post> =>
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then(
(response) => response.json()
);

export default defineComponent({
name: "PostDetails",
props: {
postId: {
type: Number,
required: true,
},
},
emits: ["setPostId"],
setup(props) {
const { isLoading, isError, isFetching, data, error } = useQuery(
["post", props.postId],
() => fetcher(props.postId)
);

return { isLoading, isError, isFetching, data, error };
},
});
</script>

<template>
<h1>Post {{ postId }}</h1>
<a @click="$emit('setPostId', -1)" href="#"> Back </a>
<div v-if="isLoading" class="update">Loading...</div>
<div v-else-if="isError">An error has occurred: {{ error }}</div>
<div v-else-if="data">
<h1>{{ data.title }}</h1>
<div>
<p>{{ data.body }}</p>
</div>
<div v-if="isFetching" class="update">Background Updating...</div>
</div>
</template>

<style scoped>
.update {
font-weight: bold;
color: green;
}
</style>
55 changes: 55 additions & 0 deletions examples/vue/basic/src/Posts.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts">
import { defineComponent } from "vue";
import { useQuery } from "@tanstack/vue-query";

import { Post } from "./types";

const fetcher = async (): Promise<Post[]> =>
await fetch("https://jsonplaceholder.typicode.com/posts").then((response) =>
response.json()
);

export default defineComponent({
name: "PostsList",
props: {
isVisited: {
type: Function,
required: true,
},
},
emits: ["setPostId"],
setup() {
const { isLoading, isError, isFetching, data, error, refetch } = useQuery(
["posts"],
fetcher
);

return { isLoading, isError, isFetching, data, error, refetch };
},
});
</script>

<template>
<h1>Posts</h1>
<div v-if="isLoading">Loading...</div>
<div v-else-if="isError">An error has occurred: {{ error }}</div>
<div v-else-if="data">
<ul>
<li v-for="item in data" :key="item.id">
<a
@click="$emit('setPostId', item.id)"
href="#"
:class="{ visited: isVisited(item.id) }"
>{{ item.title }}</a
>
</li>
</ul>
</div>
</template>

<style scoped>
.visited {
font-weight: bold;
color: green;
}
</style>
6 changes: 6 additions & 0 deletions examples/vue/basic/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createApp } from "vue";
import { VueQueryPlugin } from "@tanstack/vue-query";

import App from "./App.vue";

createApp(App).use(VueQueryPlugin).mount("#app");
5 changes: 5 additions & 0 deletions examples/vue/basic/src/shims-vue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module "*.vue" {
import { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
6 changes: 6 additions & 0 deletions examples/vue/basic/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Post {
userId: number;
id: number;
title: string;
body: string;
}
15 changes: 15 additions & 0 deletions examples/vue/basic/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
11 changes: 11 additions & 0 deletions examples/vue/basic/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
optimizeDeps: {
include: ["remove-accents"],
exclude: ["vue-query", "vue-demi"],
},
});
9 changes: 9 additions & 0 deletions packages/vue-query/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"parserOptions": {
"project": "./tsconfig.lint.json",
"sourceType": "module"
},
"rules": {
"react-hooks/rules-of-hooks": "off"
}
}
Loading