Vue 3 Integration Example
Complete Vue 3 Composition API integration examples with reusable composables.
Installation
bash
npm install bindra vue
# or
pnpm add bindra vueComposables
Core Composable: useBindra
The main composable for integrating Bindra with Vue 3:
typescript
import { useBindra } from './composables';
const { data, loading, create, update, remove } = useBindra<User>({
url: '/api/users',
cache: { enabled: true }
});Returns:
data- Reactive ref with data arraycurrent- Current record (for navigation)loading- Loading stateerror- Error statecreate(record)- Create operationupdate(id, changes)- Update operationremove(id)- Delete operationrefresh()- Refresh datadataSource- Direct DataSource access
Pagination: useBindraPagination
Composable for paginated data:
typescript
import { useBindraPagination } from './composables';
const {
data,
hasMore,
loadMore,
currentPage,
totalPages
} = useBindraPagination<Product>({
url: '/api/products',
pagination: { enabled: true, pageSize: 20 }
});Additional Returns:
hasMore- More pages availablecurrentPage- Current page numbertotalPages- Total pagestotalRecords- Total records countloadMore()- Load next pageloadPage(page)- Jump to specific page
Real-time: useBindraRealtime
Composable for WebSocket/real-time features:
typescript
import { useBindraRealtime } from './composables';
const {
data,
connectionStatus,
sendMessage
} = useBindraRealtime<Message>({
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws'
}
});Additional Returns:
connectionStatus- Connection state ('connecting' | 'connected' | 'disconnected' | 'error')sendMessage(data)- Send WebSocket message
Search: useBindraSearch
Composable with debounced search:
typescript
import { useBindraSearch } from './composables';
const {
data,
search,
searchQuery
} = useBindraSearch<Product>({
url: '/api/products'
}, 300); // 300ms debounceValidation: useBindraValidation
Composable for form validation:
typescript
import { useBindraValidation } from './composables';
const { errors, validate, clearErrors } = useBindraValidation(dataSource);
const submit = async () => {
if (validate(formData)) {
await create(formData);
}
};Examples
1. Todo List
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useBindra } from './composables';
interface Todo {
id: number;
title: string;
completed: boolean;
}
const { data: todos, create, update, remove } = useBindra<Todo>({
url: '/api/todos'
});
const newTodo = ref('');
const addTodo = async () => {
await create({ title: newTodo.value, completed: false });
newTodo.value = '';
};
const toggleTodo = async (todo: Todo) => {
await update(todo.id, { completed: !todo.completed });
};
</script>
<template>
<div>
<input v-model="newTodo" @keyup.enter="addTodo" />
<button @click="addTodo">Add</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo)"
/>
{{ todo.title }}
<button @click="remove(todo.id)">Delete</button>
</li>
</ul>
</div>
</template>2. Searchable Product List
vue
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useBindraPagination } from './composables';
import { debounce } from 'bindra';
const {
data: products,
hasMore,
loadMore,
dataSource
} = useBindraPagination({
url: '/api/products',
pagination: { enabled: true, pageSize: 20 }
});
const searchQuery = ref('');
const debouncedSearch = debounce((query: string) => {
dataSource.fetch({ search: query });
}, 300);
watch(searchQuery, (query) => {
debouncedSearch(query);
});
</script>
<template>
<div>
<input v-model="searchQuery" placeholder="Search..." />
<div v-for="product in products" :key="product.id">
{{ product.name }} - ${{ product.price }}
</div>
<button v-if="hasMore" @click="loadMore">
Load More
</button>
</div>
</template>3. Real-time Chat
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useBindraRealtime } from './composables';
const {
data: messages,
connectionStatus,
create
} = useBindraRealtime({
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws'
}
});
const messageText = ref('');
const sendMessage = async () => {
await create({
text: messageText.value,
userId: currentUser.id,
timestamp: new Date()
});
messageText.value = '';
};
</script>
<template>
<div>
<div :class="connectionStatus">
Status: {{ connectionStatus }}
</div>
<div v-for="msg in messages" :key="msg.id">
<strong>{{ msg.username }}:</strong> {{ msg.text }}
</div>
<input
v-model="messageText"
@keyup.enter="sendMessage"
:disabled="connectionStatus !== 'connected'"
/>
</div>
</template>4. Form with Validation
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useBindra, useBindraValidation } from './composables';
const { create, dataSource } = useBindra({
url: '/api/users',
validation: {
name: [
{ type: 'required', message: 'Name required' },
{ type: 'minLength', value: 3 }
],
email: [
{ type: 'required' },
{ type: 'email' }
]
}
});
const { errors, validate, clearErrors } = useBindraValidation(dataSource);
const formData = ref({ name: '', email: '', age: 0 });
const submit = async () => {
if (validate(formData.value)) {
await create(formData.value);
formData.value = { name: '', email: '', age: 0 };
clearErrors();
}
};
</script>
<template>
<form @submit.prevent="submit">
<div>
<input v-model="formData.name" placeholder="Name" />
<span v-if="errors.name" class="error">{{ errors.name }}</span>
</div>
<div>
<input v-model="formData.email" type="email" placeholder="Email" />
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<button type="submit">Submit</button>
</form>
</template>Components
ProductList.vue
Searchable product list with infinite scroll pagination.
Features:
- Debounced search
- Category filtering
- Infinite scroll
- Load more button
- Responsive grid layout
RealtimeChat.vue
Real-time chat component with WebSocket.
Features:
- WebSocket connection
- Real-time message updates
- Connection status indicator
- Auto-scroll to bottom
- Message timestamps
Features Demonstrated
- ✅ Reactive state management
- ✅ Automatic cleanup (onUnmounted)
- ✅ TypeScript support
- ✅ Pagination (infinite scroll)
- ✅ Search with debounce
- ✅ Real-time updates (WebSocket)
- ✅ Form validation
- ✅ Error handling
- ✅ Loading states
- ✅ Optimistic updates
Best Practices
1. Cleanup
Composables automatically clean up subscriptions:
typescript
onUnmounted(() => {
unsubscribers.forEach(unsub => unsub());
dataSource.disconnectWebSocket?.();
});2. Error Handling
All operations catch errors:
typescript
const create = async (record: Partial<T>) => {
try {
return await dataSource.create(record);
} catch (err) {
error.value = err as Error;
throw err;
}
};3. Reactive State
Use Vue refs for reactive state:
typescript
const data = ref<T[]>([]);
const loading = ref(false);4. Debounced Search
Always debounce search inputs:
typescript
const debouncedSearch = debounce((query) => {
dataSource.fetch({ search: query });
}, 300);5. Type Safety
Always use TypeScript generics:
typescript
const { data } = useBindra<User>({ url: '/api/users' });
// data is Ref<User[]>Project Setup
Install Dependencies
bash
npm install bindra vue
npm install -D @vue/compiler-sfc typescriptVite Config
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()]
});TypeScript Config
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve"
}
}