React Integration Example
Complete React integration examples with the useBindra custom hook.
Installation
bash
npm install bindra react react-dom
# or
pnpm add bindra react react-domCustom Hook: useBindra
The useBindra hook provides a React-friendly interface to Bindra DataSource:
typescript
function useBindra<T>(config: DataSourceConfig) {
return {
data: T[], // Reactive data array
current: T | null, // Current record
loading: boolean, // Loading state
error: Error | null, // Error state
create: (record) => Promise<T>,
update: (id, changes) => Promise<T>,
remove: (id) => Promise<void>,
refresh: () => Promise<void>,
dataSource: DataSource<T>
};
}Examples
1. Todo List
tsx
import { useBindra } from './hooks/useBindra';
function TodoList() {
const { data, loading, create, update, remove } = useBindra<Todo>({
url: '/api/todos',
cache: { enabled: true, ttl: 300000 }
});
const handleToggle = (todo: Todo) => {
update(todo.id, { completed: !todo.completed });
};
return (
<ul>
{data.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleToggle(todo)}
/>
{todo.title}
<button onClick={() => remove(todo.id)}>Delete</button>
</li>
))}
</ul>
);
}2. Searchable Product List with Pagination
tsx
function ProductList() {
const { data, loading, dataSource } = useBindra({
url: '/api/products',
pagination: { enabled: true, pageSize: 20 }
});
const debouncedSearch = useMemo(
() => debounce((query: string) => {
dataSource.fetch({ search: query });
}, 300),
[]
);
return (
<div>
<input
type="search"
onChange={(e) => debouncedSearch(e.target.value)}
/>
<div className="products">
{data.map(product => (
<ProductCard key={product.id} {...product} />
))}
</div>
{dataSource.hasMore && (
<button onClick={() => dataSource.fetchMore()}>
Load More
</button>
)}
</div>
);
}3. Real-time Chat
tsx
function Chat() {
const { data: messages, create } = useBindra({
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws'
}
});
const [message, setMessage] = useState('');
const send = () => {
create({ text: message, userId: currentUser.id });
setMessage('');
};
return (
<div>
<div className="messages">
{messages.map(msg => (
<Message key={msg.id} {...msg} />
))}
</div>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && send()}
/>
</div>
);
}4. Form with Validation
tsx
function UserForm() {
const { create, loading } = useBindra({
url: '/api/users',
validation: {
name: [
{ type: 'required', message: 'Name required' },
{ type: 'minLength', value: 3 }
],
email: [
{ type: 'required' },
{ type: 'email' }
]
},
security: {
sanitizeFields: ['name', 'bio']
}
});
const [data, setData] = useState({ name: '', email: '' });
const [errors, setErrors] = useState({});
const submit = async (e) => {
e.preventDefault();
try {
await create(data);
setData({ name: '', email: '' });
} catch (err) {
setErrors(err.validationErrors || {});
}
};
return (
<form onSubmit={submit}>
<input
value={data.name}
onChange={(e) => setData({...data, name: e.target.value})}
/>
{errors.name && <span>{errors.name}</span>}
<button disabled={loading}>Submit</button>
</form>
);
}Features
Automatic Cleanup
The useBindra hook automatically cleans up subscriptions and WebSocket connections when the component unmounts.
Reactive State
All state updates are automatic - no manual subscriptions needed:
tsx
const { data, current, loading } = useBindra(config);
// data, current, and loading update automatically
// when DataSource changesTypeScript Support
Full type safety with generics:
tsx
interface User {
id: number;
name: string;
email: string;
}
const { data, create, update } = useBindra<User>({
url: '/api/users'
});
// data is User[]
// create/update are type-safeBest Practices
Memoize DataSource config
tsxconst config = useMemo(() => ({ url: '/api/users', cache: { enabled: true } }), []);Debounce search inputs
tsxconst search = useMemo( () => debounce((q) => ds.fetch({ search: q }), 300), [] );Handle errors
tsxconst { error } = useBindra(config); if (error) { return <ErrorMessage error={error} />; }Show loading states
tsxconst { loading } = useBindra(config); return loading ? <Spinner /> : <DataList />;