AI SDK UIObject Generation

Object Generation

useObject is an experimental feature and only available in React.

The useObject hook allows you to create interfaces that represent a structured JSON object that is being streamed.

In this guide, you will learn how to use the useObject hook in your application to generate UIs for structured data on the fly.

Example

The example shows a small notfications demo app that generates fake notifications in real-time.

Schema

It is helpful to set up the schema in a separate file that is imported on both the client and server.

app/api/notifications/schema.ts
import { z } from 'zod';
// define a schema for the notifications
export const notificationSchema = z.object({
notifications: z.array(
z.object({
name: z.string().describe('Name of a fictional person.'),
message: z.string().describe('Message. Do not use emojis or links.'),
}),
),
});

Client

The client uses useObject to stream the object generation process.

The results are partial and are displayed as they are received. Please note the code for handling undefined values in the JSX.

app/page.tsx
'use client';
import { experimental_useObject as useObject } from 'ai/react';
import { notificationSchema } from './api/notifications/schema';
export default function Page() {
const { object, submit } = useObject({
api: '/api/notifications',
schema: notificationSchema,
});
return (
<>
<button onClick={() => submit('Messages during finals week.')}>
Generate notifications
</button>
{object?.notifications?.map((notification, index) => (
<div key={index}>
<p>{notification?.name}</p>
<p>{notification?.message}</p>
</div>
))}
</>
);
}

Server

On the server, we use streamObject to stream the object generation process.

app/api/notifications/route.ts
import { openai } from '@ai-sdk/openai';
import { streamObject } from 'ai';
import { notificationSchema } from './schema';
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export async function POST(req: Request) {
const context = await req.json();
const result = await streamObject({
model: openai('gpt-4-turbo'),
schema: notificationSchema,
prompt:
`Generate 3 notifications for a messages app in this context:` + context,
});
return result.toTextStreamResponse();
}

Customized UI

useObject also provides ways to show loading and error states:

Loading State

The isLoading state returned by the useObject hook can be used for several purposes

  • To show a loading spinner while the object is generated.
  • To show a "Stop" button to abort the current message.
  • To disable the submit button.
app/page.tsx
'use client';
import { useObject } from 'ai/react';
export default function Page() {
const { isLoading, stop, object, submit } = useObject({
api: '/api/notifications',
schema: notificationSchema,
});
return (
<>
{isLoading && (
<div>
<Spinner />
<button type="button" onClick={() => stop()}>
Stop
</button>
</div>
)}
<button
onClick={() => submit('Messages during finals week.')}
disabled={isLoading}
>
Generate notifications
</button>
{object?.notifications?.map((notification, index) => (
<div key={index}>
<p>{notification?.name}</p>
<p>{notification?.message}</p>
</div>
))}
</>
);
}

Error State

Similarly, the error state reflects the error object thrown during the fetch request. It can be used to display an error message, or to disable the submit button:

We recommend showing a generic error message to the user, such as "Something went wrong." This is a good practice to avoid leaking information from the server.

'use client';
import { useObject } from 'ai/react';
export default function Page() {
const { error, object, submit } = useObject({
api: '/api/notifications',
schema: notificationSchema,
});
return (
<>
{error && <div>An error occurred.</div>}
<button onClick={() => submit('Messages during finals week.')}>
Generate notifications
</button>
{object?.notifications?.map((notification, index) => (
<div key={index}>
<p>{notification?.name}</p>
<p>{notification?.message}</p>
</div>
))}
</>
);
}

Event Callbacks

useObject provides optional event callbacks that you can use to handle life-cycle events.

  • onFinish: Called when the object generation is completed.
  • onError: Called when an error occurs during the fetch request.

These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates.

app/page.tsx
'use client';
import { experimental_useObject as useObject } from 'ai/react';
import { notificationSchema } from './api/notifications/schema';
export default function Page() {
const { object, submit } = useObject({
api: '/api/notifications',
schema: notificationSchema,
onFinish({ object, error }) {
// typed object, undefined if schema validation fails:
console.log('Object generation completed:', object);
// error, undefined if schema validation succeeds:
console.log('Schema validation error:', error);
},
onError(error) {
// error during fetch request:
console.error('An error occurred:', error);
},
});
return (
<div>
<button onClick={() => submit('Messages during finals week.')}>
Generate notifications
</button>
{object?.notifications?.map((notification, index) => (
<div key={index}>
<p>{notification?.name}</p>
<p>{notification?.message}</p>
</div>
))}
</div>
);
}

Configure Request Options

You can configure the API endpoint and optional headers using the api and headers settings.

const { submit, object } = useObject({
api: '/api/use-object',
headers: {
'X-Custom-Header': 'CustomValue',
},
schema: yourSchema,
});