Queries

About 2 min

Queries

The simplest way to access data in ECHO from react is by using a useQuery hook on a space. This will return generic objects which can be mutated like regular JavaScript objects. useQuery<T> can also return strongly typed results as will be shown below.

Untyped queries

The first argument to useQuery from package @dxos/react-client is the space and the second is an optional filter which matches all objects which have all the keys and values specified in the filter. The return type is an iterable array of Document objects.

import React from 'react';
import { createRoot } from 'react-dom/client';
import {
  ClientProvider,
  useOrCreateFirstSpace,
  useIdentity,
  useQuery
} from '@dxos/react-client';

export const App = () => {
  useIdentity({ login: true });
  const space = useOrCreateFirstSpace();
  const tasks = useQuery(space, { type: 'task' });
  return <>
    {tasks?.map((task) => (
      <div key={task.id}>{task.title}</div>
    ))}
  </>;
};

const root = createRoot(document.getElementById('root')!);
root.render(
  <ClientProvider>
    <App />
  </ClientProvider>
);













 












The API definition of useQuery is below. It returns a generic Document type which supports the ability to set and read arbitrary keys and values. See below for how to add type safety.

useQuery([space], [filter])open in new window

Create subscription.

Returns: Document<object>[]

Arguments:

space: Space

filter: Filter<T>

Typed Queries

It's possible to obtain strongly typed objects from useQuery<T>.

Because useQuery returns tracked ECHO objects, their type must descend from DocumentBase. DXOS provides a tool to generate these types from a schema definition file.

There are many benefits to expressing the type schema of an application in a language-neutral and inter-operable way. One of them is the ability to generate type-safe data layer code, which makes development faster and safer.

Protobufopen in new window is well oriented towards schema migrations, while at the same time being compact and efficient on the wire and in-memory.

Consider this expression of schema declared in protobufopen in new window:

syntax = "proto3";

package example.tasks;

message Task {
  option (object) = true;

  string title = 1;
  bool completed = 2;
}

message TaskList {
  option (object) = true;

  string title = 1;
  repeated Task tasks = 2;
}





 






 




Using a tool called dxtype from @dxos/echo-schema we can generate corresponding classes for use with DXOS Client.

dxtype <input protobuf file> <output typescript file>

Note

Note the directives option (object) = true; which instruct the framework to generate TypeScript classes from the marked messages.

Tip

If you're using one of the DXOS application templates, this type generation step is pre-configured as a prebuildopen in new window script for you.

See TypeScript output from `dxtype`

The output is a typescript file that looks roughly like this:

import { DocumentBase, TypeFilter, EchoSchema } from "@dxos/react-client";

export const schema = EchoSchema.fromJson(
  '{ "protobuf generated json here": true }'
);

export class Task extends DocumentBase {
  static readonly type = schema.getType('dxos.tasks.Task');

  static filter(opts?: { title?: string, completed?: boolean }): TypeFilter<Task> {
    return Task.type.createFilter(opts);
  }

  constructor(opts?: { title?: string, completed?: boolean }) {
    super({ ...opts, '@type': Task.type.name }, Task.type);
  }

  declare title: string;
  declare completed: boolean;
}

Declared are the ancestor class and specific fields on the type.

There are other utilities like a filter you can pass to useQuery to locate items of this type.

To use the type declarations, simply import the relevant type like Task from the typescript location out of dxtype and pass it to useQuery<T>:

import React from 'react';
import { createRoot } from 'react-dom/client';
import {
  ClientProvider,
  useOrCreateFirstSpace,
  useIdentity,
  useQuery
} from '@dxos/react-client';

import { Task } from './schema';

export const App = () => {
  useIdentity({ login: true });
  const space = useOrCreateFirstSpace();
  const tasks = useQuery<Task>(space, Task.filter());
  return <>
    {tasks?.map((task) => (
      <div key={task.id}>{task.title} - {task.completed}</div>
    ))}
  </>;
};

const root = createRoot(document.getElementById('root')!);
root.render(
  <ClientProvider>
    <App />
  </ClientProvider>
);