Overview
React components in KOS applications serve as the visualization layer for device state maintained by KOS models. This architectural pattern significantly simplifies React components compared to traditional approaches.
Simplified Components Through Models
By placing business logic, device communication, and state management in KOS models, React components become dramatically simpler:
- No useState for business state – Model properties provide reactive state automatically
- No useEffect for data fetching or subscriptions – Models handle device communication and lifecycle
- No manual event listeners – Observable models notify components of changes automatically
- Direct reflection of state – Components simply render current model values
Components only use React hooks like useState and useEffect for UI-specific concerns like animations, local form state, or DOM interactions. All business logic, device state, and application data lives in models where it can be tested, reused, and managed independently of the UI.
Observable Models and Efficient Rendering
KOS models are observable, meaning they automatically track which components reference which properties. When a model property changes, only components that actually use that property will re-render. This targeted rendering ensures React components remain responsive even with frequent device updates, without manual optimization or memoization.
Understanding how to connect models to React components is fundamental to building responsive, efficient KOS applications. The framework provides two primary integration patterns: passing models as props and accessing models via hooks.
Getting Models into Components
There are two ways to access models in React components:
- Props – Models passed from parent components
- Hooks – Models accessed via React hooks
Using Props
Pass model instances directly to child components:
function Dashboard() {
const pumpContainer = usePumpContainerModel('main');
return (
<div>
{pumpContainer.pumps.map(pump => (
<PumpCard key={pump.id} pump={pump} />
))}
</div>
);
}
// Child component receives model as prop
const PumpCard = kosComponent(({ pump }: { pump: PumpModel }) => {
return (
<div>
<h3>{pump.name}</h3>
<div>Rate: {pump.rate}</div>
</div>
);
});Using Hooks
Base Hook – useKosModel
The generic useKosModel hook provides access to any model:
import { useKosModel } from '@kosdev-code/kos-ui-sdk';
function DeviceStatus() {
const { model: device, ready } = useKosModel({
modelId: 'device-1',
modelType: 'device-model'
});
if (!ready) return <div>Loading...</div>;
return <div>Status: {device.status}</div>;
}Hook Lifecycle Options
The useKosModel hook provides three important options for managing model lifecycle:
activate – Triggers the model’s activate() lifecycle method when the component mounts, enabling lazy loading of data only when the UI needs it. When the component unmounts, the model’s deactivate() method is called, allowing cleanup of active operations:
function Dashboard() {
const { model: analytics, ready } = useKosModel({
modelId: 'analytics',
modelType: 'analytics-model',
activate: true // Calls model.activate() on mount, model.deactivate() on unmount
});
if (!ready) return <div>Loading...</div>;
return <div>{analytics.summary}</div>;
}destroyOnUnmount – Completely destroys the model when the component unmounts, unregistering topic handlers and cleaning up all resources. Use this for models that should not persist beyond the component’s lifetime:
function TemporaryView() {
const { model: tempData } = useKosModel({
modelId: 'temp-data',
modelType: 'data-model',
destroyOnUnmount: true // Model completely removed when component unmounts
});
return <div>{tempData.value}</div>;
}forceUpdate – When true and options are provided, updates the existing model instance with new options. When false (default), reuses the existing model as-is if it already exists:
function ConfigurableView({ filter }: { filter: string }) {
const { model: data } = useKosModel({
modelId: 'data',
modelType: 'data-model',
options: { filter },
forceUpdate: true // Applies new filter even if model already exists
});
return <div>{data.results}</div>;
}CLI-Generated Hooks
Generate strongly-typed hooks for your models using the kosui CLI:
npx kosui hookThis creates a hook specific to your model with proper TypeScript types:
// Generated hook provides type safety
import { usePumpModel } from './pump-model-hooks';
function PumpControl() {
const pump = usePumpModel('pump-s1');
return (
<div>
<div>Rate: {pump.rate}</div>
<button onClick={() => pump.calibrate()}>
Calibrate
</button>
</div>
);
}Performance Considerations with Hooks
When rendering lists, avoid using hooks in every child component. Instead, use hooks at a higher level and pass models as props:
// Good - Hook at container level, props to children
function PumpList() {
const container = usePumpContainerModel('main');
return (
<div>
{container.pumps.map(pump => (
<PumpCard key={pump.id} pump={pump} />
))}
</div>
);
}
// Avoid - Hook in every list item creates overhead
function PumpList() {
return (
<div>
{pumpIds.map(id => (
<PumpCardWithHook key={id} pumpId={id} />
))}
</div>
);
}Model-Aware Components
Create components that react to model changes using kosComponent:
import { kosComponent } from '@kosdev-code/kos-ui-sdk';
export const TemperatureDisplay = kosComponent(({ sensor }) => {
return (
<div>
<div>Current: {sensor.temperature}°C</div>
<div>Status: {sensor.status}</div>
</div>
);
});What kosComponent Does
The kosComponent wrapper makes your component an observer of the model. When any referenced model property changes, the component automatically re-renders.
// This component only re-renders when temperature or status change
export const TemperatureDisplay = kosComponent(({ sensor }) => {
return (
<div>
<div>{sensor.temperature}°C</div>
<div>{sensor.status}</div>
</div>
);
});Generate Components with CLI
Use the kosui CLI to generate model-aware components:
npx kosui componentThe generated component includes the kosComponent wrapper:
import { kosComponent } from '@kosdev-code/kos-ui-sdk';
export const MyComponent = kosComponent(({ onComplete }) => {
return (
<div>
{/* Component implementation */}
</div>
);
});Efficient Reactive Rendering
Models are observable and participate efficiently with React. Only components that reference changed properties re-render:
@kosModel({ modelTypeId: "pump-model", singleton: false })
export class PumpModelImpl implements IKosDataModel {
name: string = "";
rate: number = 0;
status: string = "idle";
@kosTopicHandler({ topic: "/device/pump/rate" })
handleRateUpdate(data: { rate: number }): void {
this.rate = data.rate;
// Only components referencing 'rate' will re-render
}
}
// This component re-renders when rate changes
const RateDisplay = kosComponent(({ pump }) => (
<div>Rate: {pump.rate}</div>
));
// This component does NOT re-render when rate changes
const StatusDisplay = kosComponent(({ pump }) => (
<div>Status: {pump.status}</div>
));This targeted rendering ensures React components remain responsive even with frequent device updates.
Summary
- Props vs Hooks: Use props for child components, hooks for top-level access
- useKosModel: Generic hook for any model
- CLI Hooks: Generate typed hooks with
npx kosui hook - Performance: Use hooks at container level, pass models as props to children
- kosComponent: Wraps components to observe model changes
- CLI Components: Generate with
npx kosui component - Efficient Rendering: Only components using changed properties re-render
See the Configuration Management guide for accessing device configuration in models, and the Event-Driven Communication guide for handling real-time device events.
