@convex-vue/core v0.0.4
@convex-vue/core
Get Started
- Install the package and its peer dependencies
npm install @convex-vue/core @vueuse/core convex vue-routerSimple example
const convexVue = createConvexVue({
convexUrl: import.meta.env.VITE_CONVEX_URL
});
app.use(convexVue);Example with auth using auth0
// Example with auth using auth0
const auth = createAuth0({
domain: import.meta.env.VITE_AUTH0_DOMAIN,
clientId: import.meta.env.VITE_AUTH0_CLIENTID,
authorizationParams: {
redirect_uri: window.location.origin
}
});
const convexVue = createConvexVue({
convexUrl: import.meta.env.VITE_CONVEX_URL,
auth: {
isAuthenticated: auth.isAuthenticated,
isLoading: auth.isLoading,
getToken: async ({ forceRefreshToken }) => {
try {
const response = await auth.getAccessTokenSilently({
detailedResponse: true,
cacheMode: forceRefreshToken ? 'off' : 'on'
});
return response.id_token;
} catch (error) {
return null;
}
},
installNavigationGuard: true,
needsAuth: to => to.meta.needsAuth
redirectTo: () => ({
name: 'Login'
})
}
});
app.use(convexVue);- You can now use the convex-vue composables and components in your app đ
Composables
useConvexQuery
Subscribes to a convex query. It expose a suspense function to enable use inside a <Suspense /> boundary.
<script setup lang="ts">
import { api } from '../convex/_generated/api';
const { data, isLoading, error, suspense } = useConvexQuery(
api.todos.list, // the query name
{ completed: true } // query arguments, if no arguments you need to pass an empty object. It can be ref
);
await suspense(); // if used, must be called as a child of <Suspense/> component
</script>useConvexPaginatedQuery
Subscribes to a convex query and handles pagination. It expose a suspense function to enable use inside a <Suspense /> boundary that will load the first page.
<script setup lang="ts">
import { api } from '../convex/_generated/api';
const {
data,
lastPage,
isLoading,
isLoadingMore,
isDone,
loadMore,
reset,
pages,
error,
suspense,
} = useConvexPaginatedQuery(
api.todos.list, // the query name
{ completed: true } // query arguments, if no arguments you need to pass an empty object. It can be ref,
{ numItems: 50 } // the number of items per page
);
await suspense(); // if used, must be called as a child of <Suspense/> component
</script>useConvexMutation
Handles convex mutations. Optimistic updates are supported.
const { isLoading, error, mutate: addTodo } = useConvexMutation(api.todos.add, {
onSuccess() {
todo.value = '';
},
onError(err) {
console.error(err)
},
optimisticUpdate(ctx) {
const current = ctx.getQuery(api.todos.list, {});
if (!current) return;
ctx.setQuery(api.todos.list, {}, [
{
_creationTime: Date.now(),
_id: 'optimistic_id' as Id<'todos'>,
completed: false,
text: todo.text
},
...current
]);
}
});useConvexAction
Handles convex actions.
const { isLoading, error, mutate } = useConvexAction(api.some.action, {
onSuccess(result) {
console.log(result);
},
onError(err) {
console.error(err);
}
});Components
Convex-vue exposes some helpers components to use queries. This can be useful if you solely need it's data in your component templates
<ConvexQuery :query="api.todos.list" :args="{}">
<template #loading>Loading todos...</template>
<template #error="{ error }">{{ error }}</template>
<template #empty>No todos yet.</template>
<template #default="{ data: todos }">
<ul>
<li v-for="todo in todos" :key="todo._id">
<Todo :todo="todo" />
</li>
</ul>
</template>
</ConvexQuery> <ConvexPaginatedQuery
:query="api.todos.paginatedList"
:args="{}"
:options="{ numItems: 5 }"
>
<template #loading>Loading todos...</template>
<template #error="{ error, reset }">
<p>{{ error }}</p>
<button @click="reset">Retry</button>
</template>
<template #default="{ data: todos, isDone, loadMore, isLoadingMore, reset }">
<ul>
<li v-for="todo in todos" :key="todo._id">
<Todo :todo="todo" />
</li>
</ul>
<Spinner v-if="isLoadingMore" />
<footer>
<button :disabled="isDone" @click="loadMore">Load more</button>
<button @click="reset">Reset</button>
</footer>
</template>
</ConvexPaginatedQuery>đ§Ş Route Loaders (experimental)
Taking inspiration from Remix's route loaders , Convex-vue introduces a mechanism to specify which data a route needs. The data will then start fetching when navigating, before loading the javascript for the page and mouting its component. Under the hood, this fires a Convex client subscription so that, hopefully, by the time the page mounts, the convex client cache will already have the data, or , at least, the request wil lalready be in flight.
You need to use vue-router to use this feature.
- First, define a route loader map liek below.
import { api } from '@api';
import { defineRouteLoader } from '@convex-vue/core';
// defineRouteLoader will provide you with type safety
export const loaders = {
Home: defineRouteLoader({
todos: {
query: api.todos.list,
args() {
return {};
}
}
}),
TodoDetails: defineRouteLoader({
todo: {
query: api.todos.byId,
args(route) {
return {
id: route.params.id as Id<'Todos'>
};
}
}
})
};
export type Loaders = typeof loaders;- Then, pass the rotue loader map to the convex-vue plugin
import { loaders } from './loaders';
const convexVue = createConvexVue({
convexUrl: import.meta.env.VITE_CONVEX_URL,
routeLoaderMap: loaders
});
app.use(convexVue);That's it ! Now, when navigating to a page, convex-vue will look in your loader map and search for a loader corresponding to the
nameproperty of the route. Alternatively, you can provide aloaderproperty in your routesmeta, andit will be used instead.You can also use the
useRouteLoaderto get akk the data for one loader in one go, instead of using multiple instances ofuseConvexQueryoruseConvexPaginatedQuery.
const { todos } = useRouteLoader<Loaders['Home']>();â ď¸ Due to the way vue-router's route matching works, you will get the data for ALL the matched routes when using nested routes. Be wary of naming conflicts !
- A
<ConvexLink />component is also available. It just wraps vue-router's<RouterLink />and will prefetch its target loader on hover. The component accepts aprefetchTimeoutprop to set how long the link should be hovered in order to start prefetching
<ConvexLink :to="{ name: 'Home' }">Todos</ConvexLink>