@qhel/react
v0.1.0
Published
React bindings for @qhel/sdk: reactive live workspaces via useSyncExternalStore. React Native/Expo first, browser too.
Maintainers
Readme
@qhel/react
Bindings de React para @qhel/sdk: workspaces en vivo, reactivos, vía
useSyncExternalStore. Pensado para React Native / Expo primero, y también para
navegador. Una sola conexión compartida por todos los hooks bajo el provider.
- Estado en vivo:
useWorkspace(ws)re-renderiza cuando cambia el workspace. - Escrituras:
useQhelActions()(create/update/remove + búsquedas semánticas). - Conexión como producto (ADR-009):
useConnectionState()yuseReady(). - Seguro en StrictMode:
connect/disposeson idempotentes y con recuento de referencias (el doble montaje de StrictMode no abre sockets duplicados).
Instalación
npm install @qhel/react @qhel/sdk
# o: pnpm add @qhel/react @qhel/sdkreact (>=18) es una peer dependency: la aporta tu app (RN/Expo o navegador).
useSyncExternalStore requiere React 18 o superior.
Quickstart
Crea el cliente una vez y envuelve tu árbol con QhelProvider. Las credenciales
(url y token) se obtienen del panel de Qhel; el alcance del token es a nivel
de proyecto (ver el README del SDK antes de embeberlo en una
app pública).
import { Qhel } from "@qhel/sdk";
import { QhelProvider, useWorkspace, useQhelActions, useConnectionState } from "@qhel/react";
const db = new Qhel({ url: "wss://<tu-engine>", token: "<token-del-proyecto>" });
export function App() {
return (
<QhelProvider
client={db}
// El "wow": tu creación se parece a algo EXISTENTE, detectado por significado.
handlers={{
onDuplicate: (item, matchId, score, sourceId) =>
console.log(`"${sourceId}" ≈ "${matchId}" (${Math.round(score * 100)}%)`, item),
}}
>
<Inbox />
</QhelProvider>
);
}
function Inbox() {
const items = useWorkspace("inbox"); // en vivo: se re-renderiza al cambiar
const { create, remove } = useQhelActions();
const state = useConnectionState(); // connecting | connected | reconnecting | offline
return (
<>
<span>{state}</span>
<button onClick={() => create("inbox", { id: crypto.randomUUID(), workspace: "inbox", fields: { title: "Nueva tarea" } })}>
Añadir
</button>
<ul>
{items.map((item) => (
<li key={item.id} onClick={() => remove("inbox", item.id)}>
{String(item.fields.title ?? "")}
</li>
))}
</ul>
</>
);
}En React Native usa
newId()de@qhel/sdken lugar decrypto.randomUUID().
API
| Símbolo | Qué hace |
| --- | --- |
| QhelProvider | Posee la conexión (connect al montar, dispose al desmontar) y reparte el store por contexto. Reenvía handlers cambiantes sin reconstruir la conexión. |
| useWorkspace(ws) | readonly Item[] en vivo del workspace. Al usarlo por primera vez lo prima (list inicial + subscribe). La referencia del array es estable hasta que el workspace cambia de verdad. |
| useQhelActions() | Acciones estables de uso común: create, update, remove, check, similar, search. El resto de la API (abajo) va por useQhelClient(). |
| useConnectionState() | Estado de conexión (S1). |
| useReady() | true cuando el handshake ya se completó (seguro para leer/escribir en vivo). |
| useQhelClient() | El cliente Qhel subyacente, para las operaciones no cubiertas por useQhelActions: query (WHERE/orderBy/keyset), count, getMany, increment/decrement, transaction, removeField, export/Qhel.toImportable, bulkLoad, list, clearWorkspace, y CAS (expectedVersion en update/remove). |
| useQhelStore() | El store crudo (uso avanzado). Lanza fuera de un QhelProvider. |
Offline-first
Las escrituras se enrutan en el propio SDK: en vivo si hay conexión, o a la cola CRDT
offline si no, reconciliadas al reconectar (ADR-008). Para que la cola sobreviva a un
cierre de la app, pasa storage al construir el Qhel (ver el README del SDK); estos
hooks no necesitan configuración adicional para ello.
Licencia
SEE LICENSE IN LICENSE.
