@object-ui/react
v17.6.0
Published
React bindings and SchemaRenderer component for Object UI
Maintainers
Readme
@object-ui/react
React bindings and SchemaRenderer component for Object UI.
Features
- ⚛️ SchemaRenderer - Main component for rendering Object UI schemas
- 🪝 React Hooks - Hooks for accessing renderer context
- 🔄 Context Providers - React Context for state management
- 📦 Tree-Shakable - Import only what you need
Installation
npm install @object-ui/react @object-ui/corePeer Dependencies:
react^18.0.0 || ^19.0.0react-dom^18.0.0 || ^19.0.0
Usage
Basic Example
import { SchemaRenderer } from '@object-ui/react'
const schema = {
type: 'text',
value: 'Hello, Object UI!'
}
function App() {
return <SchemaRenderer schema={schema} />
}With Data
import { SchemaRenderer } from '@object-ui/react'
const schema = {
type: 'form',
body: [
{
type: 'input',
name: 'name',
label: 'Name',
value: '${user.name}'
}
]
}
const data = {
user: { name: 'John Doe' }
}
function App() {
return <SchemaRenderer schema={schema} data={data} />
}Handling Actions
import { SchemaRenderer } from '@object-ui/react'
function App() {
const handleSubmit = (data) => {
console.log('Form submitted:', data)
}
return (
<SchemaRenderer
schema={formSchema}
onSubmit={handleSubmit}
/>
)
}SchemaRendererProvider
Injects the host's data source (and optional capabilities) into every renderer below it:
import { SchemaRendererProvider } from '@object-ui/react'
<SchemaRendererProvider
dataSource={adapter}
// Optional: host-authenticated fetch used by `provider: 'api'` view data
// sources, so custom endpoints carry the same credentials (Authorization,
// tenant, locale headers) as the native data channel. When omitted,
// ApiDataSource falls back to the bare global fetch (cookies only).
apiFetch={authenticatedFetch}
>
<SchemaRenderer schema={schema} />
</SchemaRendererProvider>Nested providers inherit apiFetch from their parent when they don't supply
their own, so re-wrapped subtrees (embedded pages, preview surfaces) keep the
host's authentication.
Hooks
useSchemaContext
Access the current schema context:
import { useSchemaContext } from '@object-ui/react'
function MyComponent() {
const { data, updateData } = useSchemaContext()
return <div>{data.value}</div>
}useElementDataSourceSchema / ElementDataSourceGate
Consume PageComponentSchema.dataSource — the spec's per-element data binding
({ object, view?, filter?, sort?, limit? }) — in a block that has its own key
names. useElementDataSource resolves the binding (fetching the object's saved
views so view can be matched); these two apply the composed result to the
block's schema and render the two non-final states.
import { ElementDataSourceGate } from '@object-ui/react'
// `mapping` names ONLY the keys this block reads. A composed value written onto
// a key the block ignores would be accepted and silently dropped — the defect
// the binding exists to remove.
const OBJECT_GRID_BINDING = {
columns: true, // the view's FIELD list may fill `schema.columns`
filter: true, // AND-combined, never replaced ("additional criteria")
sort: true,
limit: 'pagination.pageSize' as const,
}
const ObjectGridRenderer = ({ schema, ...props }) => (
<ElementDataSourceGate schema={schema} mapping={OBJECT_GRID_BINDING} testId="object-grid">
{(bound) => <ObjectGrid schema={bound} {...props} />}
</ElementDataSourceGate>
)object lands on objectName by default (pass object: 'apiName' for another
key, or object: false for a block that reads the composed binding itself).
Precedence: binding keys beat the component's own, view-sourced values are only a
baseline the component's own key overrides, and filter AND-combines all three.
A view name that does not resolve renders a configuration error rather than
falling back to the object's full scope. Use useElementDataSourceSchema (plus
the exported ElementDataSourceErrorPanel / ElementDataSourceLoadingPanel) when
a block cannot be wrapped — a renderer whose hooks must run before the panels.
ComponentRegistry
There is no registry hook: the registry is a process-level singleton exported
by @object-ui/core, so read it directly. Subscribe to it only when a lazily
registered plugin must trigger a re-render.
import { ComponentRegistry } from '@object-ui/core'
function MyComponent(props: Record<string, unknown>) {
const Component = ComponentRegistry.get('button')
return Component ? <Component {...props} /> : null
}useDiscovery
Access server discovery information including preview mode detection:
import { useDiscovery } from '@object-ui/react'
function MyComponent() {
const { discovery, isLoading, isAuthEnabled, isAiEnabled } = useDiscovery()
// Check if the server is in preview mode
if (discovery?.mode === 'preview') {
console.log('Preview mode active:', discovery.previewMode)
}
// Check if AI service is available
if (isAiEnabled) {
console.log('AI service route:', discovery?.services?.ai?.route)
}
return <div>Server: {discovery?.name}</div>
}DiscoveryInfo
| Property | Type | Description |
| --- | --- | --- |
| name | string | Server name |
| version | string | Server version |
| mode | string | Runtime mode (e.g. 'development', 'production', 'preview') |
| previewMode | object | Preview mode configuration (present when mode is 'preview') |
| services | object | Service availability status (auth, data, metadata, ai) |
| capabilities | string[] | API capabilities |
The previewMode object contains:
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| autoLogin | boolean | true | Skip login/registration pages |
| simulatedRole | 'admin' \| 'user' \| 'viewer' | 'admin' | Simulated user role |
| simulatedUserName | string | 'Preview User' | Display name |
| readOnly | boolean | false | Read-only mode |
| expiresInSeconds | number | 0 | Session duration (0 = no expiry) |
| bannerMessage | string | — | UI banner message |
useNotifications / useNotificationsByPresentation
NotificationProvider implements the spec NotificationSchema. A notification's
severity picks its icon and tone; its displayType picks the surface that
renders it — and each of the five spec types has a distinct one:
| displayType | Presentation | Rendered by | Auto-dismiss |
| --- | --- | --- | --- |
| toast | transient overlay | the onToast delegate | yes |
| snackbar | bottom-anchored bar, one at a time, one action | <NotificationSnackbar /> | yes |
| banner | page-width strip in the content flow | <NotificationBanners /> | no |
| alert | blocking acknowledgement dialog (FIFO) | <NotificationAlerts /> | no |
| inline | in place, at the raising surface | <NotificationInline /> | no |
The surface components ship in @object-ui/components; mount them where they
belong (a banner is in flow, an inline notification sits next to its raiser).
onToast receives only toast items — it used to receive all five, which is
why every type looked like a toast.
const { notify } = useNotifications()
notify({ title: 'Saved', severity: 'success' }) // toast (spec default)
notify({ title: 'Viewing a draft', severity: 'warning', displayType: 'banner' })
notify({ title: 'Fix 2 fields', severity: 'error', displayType: 'inline', scope: 'contact-form' })A surface component subscribes with useNotificationsByPresentation(type, scope?),
which also registers the surface — raising a banner with no banner surface
mounted warns in dev instead of vanishing.
config is the spec NotificationConfigSchema (defaultPosition,
defaultDuration, maxVisible, stackDirection, pauseOnHover); the legacy
position / stacking spellings still resolve through
resolveNotificationConfig. Three helpers apply it so every surface agrees:
resolveNotificationPosition (a declared position always wins; nothing declared
leaves the surface on its own anchor), visibleNotificationStack (maxVisible +
stackDirection), and the context's pauseAutoDismiss / resumeAutoDismiss
(pauseOnHover). See the
notifications guide.
API Reference
See full documentation for detailed API reference.
Links
License
MIT — see LICENSE.
