Skip to main content

Querying Data

This guide covers all the ways to fetch data from the server using OSDK React hooks.

useOsdkObjects

Experimental - import from @osdk/react/experimental

Retrieve and observe collections of objects with automatic cache management.

Basic Usage

import { Todo } from "@my/osdk";
import {
type UseOsdkListResult,
useOsdkObjects,
} from "@osdk/react/experimental";

function TodoList() {
const {
data,
isLoading,
isOptimistic,
error,
fetchMore,
}: UseOsdkListResult<typeof Todo> = useOsdkObjects(Todo);

if (!data && isLoading) {
return "Loading...";
}

return (
<div>
{isLoading && <div>Refreshing data</div>}
{data.map(todo => <TodoView key={todo.$primaryKey} todo={todo} />)}
</div>
);
}

Return Values

  • data - Array of objects matching the query (undefined while initially loading)
  • isLoading - True while fetching data from server (can be true while data exists during revalidation)
  • isOptimistic - True if the list order is affected by optimistic updates (see note below)
  • fetchMore - Function to load next page (undefined when no more pages available)
  • error - Error object if fetch failed
About isOptimistic

isOptimistic refers to whether the ordered list of objects (considering only primary keys) is optimistic. To check if individual object contents are optimistic, use useOsdkObject on each object.

Filtering with where

const { data, isLoading } = useOsdkObjects(Todo, {
where: { text: { $startsWith: "cool " } },
});

Sorting with orderBy

const { data } = useOsdkObjects(Todo, {
orderBy: { createdAt: "desc" },
});

Pagination

Control page size and load more results:

function TodoList() {
const { data, isLoading, fetchMore } = useOsdkObjects(Todo, {
pageSize: 10,
});

return (
<div>
{data?.map(todo => <TodoView key={todo.$primaryKey} todo={todo} />)}

{fetchMore && (
<button onClick={() => fetchMore()} disabled={isLoading}>
{isLoading ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}

Auto-Fetching Pages

By default, only the first page is fetched. Use autoFetchMore to load more automatically:

// Fetch all pages automatically
const { data, isLoading } = useOsdkObjects(Todo, {
autoFetchMore: true,
});

// Fetch at least 100 items
const { data, isLoading, fetchMore } = useOsdkObjects(Todo, {
autoFetchMore: 100,
pageSize: 25, // Will load 4 pages
});
Performance Warning

Using autoFetchMore: true on large datasets may cause long load times and high memory usage. Prefer autoFetchMore: N with a specific number.

Conditional Queries with enabled

Control when a query executes:

import { Todo } from "@my/osdk";
import { useOsdkObjects } from "@osdk/react/experimental";
import { useState } from "react";

function ConditionalTodoFetch() {
const [shouldFetch, setShouldFetch] = useState(false);

const { data, isLoading } = useOsdkObjects(Todo, {
where: { isComplete: false },
enabled: shouldFetch, // Only fetch when true
});

return (
<div>
<button onClick={() => setShouldFetch(!shouldFetch)}>
{shouldFetch ? "Hide" : "Show"} Incomplete Todos
</button>

{shouldFetch && isLoading && !data && <div>Loading...</div>}
{data?.map(todo => <div key={todo.$primaryKey}>{todo.title}</div>)}
</div>
);
}

Real-time Updates with streamUpdates

Enable WebSocket-based real-time updates:

import { Todo } from "@my/osdk";
import { useOsdkObjects } from "@osdk/react/experimental";

function LiveTodoList() {
const { data, isLoading } = useOsdkObjects(Todo, {
where: { isComplete: false },
orderBy: { createdAt: "desc" },
streamUpdates: true,
});

// Data automatically updates when:
// - New todos matching the where clause are created
// - Existing todos are modified and still match
// - Todos are deleted or no longer match

if (isLoading && !data) {
return <div>Loading todos...</div>;
}

return (
<div>
<h2>Live Todo List ({data?.length})</h2>
{data?.map(todo => (
<div key={todo.$primaryKey}>
<span>{todo.title}</span>
{isLoading && <span style={{ fontSize: "0.8em" }}>(Updating...)</span>}
</div>
))}
</div>
);
}

Set Intersections with intersectWith

Find objects matching multiple where clauses:

import { Employee } from "@my/osdk";
import { useOsdkObjects } from "@osdk/react/experimental";

function EmployeesIntersection() {
const { data, isLoading } = useOsdkObjects(Employee, {
where: { department: "Engineering" },
intersectWith: [
{ where: { salary: { $gte: 100000 } } },
{ where: { yearsExperience: { $gte: 5 } } },
],
orderBy: { fullName: "asc" },
});

if (isLoading && !data) {
return <div>Loading...</div>;
}

return (
<div>
<h3>Senior Engineers with High Salary ({data?.length})</h3>
{data?.map(employee => (
<div key={employee.$primaryKey}>{employee.fullName}</div>
))}
</div>
);
}

Traverse relationships and return linked objects:

import { Employee } from "@my/osdk";
import { useOsdkObjects } from "@osdk/react/experimental";

function ManagerReports() {
const { data, isLoading } = useOsdkObjects(Employee, {
where: { fullName: "John Smith" },
pivotTo: "reports", // Changes return type from Employee[] to Report[]
});

if (isLoading && !data) {
return <div>Loading...</div>;
}

return (
<div>
<h3>John Smith's Direct Reports ({data?.length})</h3>
{data?.map(report => (
<div key={report.$primaryKey}>{report.fullName}</div>
))}
</div>
);
}

All Options

const { data, isLoading, isOptimistic, fetchMore, error } = useOsdkObjects(
Todo,
{
where: { isComplete: false },
pageSize: 20,
orderBy: { createdAt: "desc" },
dedupeIntervalMs: 5000,
streamUpdates: true,
autoFetchMore: 100,
enabled: true,
intersectWith: [{ where: { priority: "high" } }],
pivotTo: "assignee",
withProperties: { /* see advanced-queries */ },
},
);

useOsdkObject

Experimental - import from @osdk/react/experimental

Retrieve and observe a single object.

Tracking an Existing Instance

Pass an object instance to track its loading and optimistic state:

import { Todo } from "@my/osdk";
import { useOsdkObject } from "@osdk/react/experimental";

function TodoView({ todo }: { todo: Todo.OsdkInstance }) {
const { object, isLoading, isOptimistic, error } = useOsdkObject(todo);

return (
<div>
{object?.title || todo.title}
{isLoading && " (Loading)"}
{isOptimistic && " (Optimistic)"}
{error && <div>Error: {error.message}</div>}
</div>
);
}

Loading by Primary Key

Fetch an object by its type and primary key:

import { Todo } from "@my/osdk";
import { useOsdkObject } from "@osdk/react/experimental";

function TodoLoader({ todoId }: { todoId: string }) {
const { object, isLoading, error } = useOsdkObject(Todo, todoId);

if (isLoading && !object) {
return <div>Loading todo...</div>;
}

if (error) {
return <div>Error loading todo: {error.message}</div>;
}

if (!object) {
return <div>Todo not found</div>;
}

return <TodoView todo={object} />;
}

Return Values

  • object - The object instance (may be undefined while loading)
  • isLoading - True while fetching from server
  • isOptimistic - True if object has optimistic updates applied
  • error - Error object if fetch failed

Experimental - import from @osdk/react/experimental

Observe and navigate relationships between objects.

Basic Usage

import { Employee } from "@my/osdk";
import { useLinks } from "@osdk/react/experimental";

function EmployeeReports({ employee }: { employee: Employee.OsdkInstance }) {
const { links, isLoading, fetchMore, hasMore } = useLinks(
employee,
"reports",
{
pageSize: 10,
orderBy: { name: "asc" },
where: { isActive: true },
},
);

if (isLoading && !links) {
return <div>Loading reports...</div>;
}

return (
<div>
<h3>Reports ({links?.length})</h3>
{links?.map(report => <div key={report.$primaryKey}>{report.name}</div>)}

{hasMore && (
<button onClick={() => fetchMore?.()} disabled={isLoading}>
Load More
</button>
)}
</div>
);
}

Multiple Source Objects

Load links from multiple objects at once:

function TeamMembers({ employees }: { employees: Employee.OsdkInstance[] }) {
const { links, isLoading } = useLinks(employees, "reports");

return (
<div>
<h3>All Team Reports</h3>
{links?.map(report => <div key={report.$primaryKey}>{report.name}</div>)}
</div>
);
}

Lazy Loading with enabled

import { Employee } from "@my/osdk";
import { useLinks } from "@osdk/react/experimental";
import { useState } from "react";

function OptionalReportsList({ employee }: { employee: Employee.OsdkInstance }) {
const [showReports, setShowReports] = useState(false);

const { links, isLoading } = useLinks(employee, "reports", {
enabled: showReports,
});

return (
<div>
<button onClick={() => setShowReports(!showReports)}>
{showReports ? "Hide" : "Show"} Reports
</button>

{showReports && isLoading && !links && <div>Loading...</div>}
{links?.map(report => <div key={report.$primaryKey}>{report.name}</div>)}
</div>
);
}

Options

  • where - Filter linked objects
  • pageSize - Number of links per page
  • orderBy - Sort order for linked objects
  • mode - Fetch mode: "force" (always fetch), "offline" (cache only), or undefined (default)
  • enabled - Enable/disable the query (default: true)

Return Values

  • links - Array of linked objects
  • isLoading - True while fetching
  • isOptimistic - True if links affected by optimistic updates
  • fetchMore - Function to load next page
  • hasMore - True if more pages available
  • error - Error object if fetch failed

useOsdkClient

Stable - available from both @osdk/react and @osdk/react/experimental

Access the OSDK client directly for custom queries.

import { Todo } from "@my/osdk";
import { useOsdkClient } from "@osdk/react";

function MyComponent() {
const client = useOsdkClient();

const loadTodos = async () => {
const todos = await client(Todo).fetchPage();
// ...
};

return <button onClick={loadTodos}>Load Todos</button>;
}

Use this when you need to perform queries outside the reactive hook system, such as in event handlers or effects where you manage state manually.


Error Handling

All hooks return an error field. A basic pattern:

import { Todo } from "@my/osdk";
import { useOsdkObjects } from "@osdk/react/experimental";

function TodoList() {
const { data, isLoading, error } = useOsdkObjects(Todo);

if (error) {
return (
<div>
<h2>Error loading todos</h2>
<p>{JSON.stringify(error)}</p>
<button onClick={() => window.location.reload()}>Retry</button>
</div>
);
}

if (isLoading && !data) {
return <div>Loading todos...</div>;
}

return (
<div>
{data?.map(todo => <TodoView key={todo.$primaryKey} todo={todo} />)}
</div>
);
}

Combining Multiple Hooks

A common pattern is using multiple hooks together:

import { Todo } from "@my/osdk";
import { useLinks, useOsdkObject } from "@osdk/react/experimental";

function TodoWithDetails({ todoId }: { todoId: string }) {
const { object: todo, isLoading: todoLoading } = useOsdkObject(Todo, todoId);

const { links: comments, isLoading: commentsLoading } = useLinks(
todo,
"comments",
{ orderBy: { createdAt: "desc" } },
);

if (todoLoading) return <div>Loading...</div>;
if (!todo) return <div>Todo not found</div>;

return (
<div>
<h2>{todo.title}</h2>
<p>{todo.description}</p>

<h3>Comments ({comments?.length || 0})</h3>
{commentsLoading && <div>Loading comments...</div>}
{comments?.map(comment => (
<div key={comment.$primaryKey}>{comment.text}</div>
))}
</div>
);
}