# JSON-Driven UI Architecture Guide
## Overview
This application uses a **declarative JSON-driven architecture** that allows you to build entire user interfaces from configuration rather than code. Combined with **atomic components** (all under 150 LOC) and **custom hooks** for business logic, this creates a highly maintainable and rapidly prototype-able system.
## Core Concepts
### 1. JSON Schema Definition
Define your entire UI using JSON schemas with:
- **Data Sources**: KV store, computed values, static data
- **Components**: Shadcn UI components with props and bindings
- **Actions**: CRUD operations, toasts, navigation
- **Events**: User interactions that trigger actions
### 2. Atomic Components
Small, focused, reusable components:
- **Atoms** (< 50 LOC): ActionButton, IconButton, DataList, LoadingSpinner
- **Molecules** (50-100 LOC): StatCard, EmptyState, SearchBar
- **Organisms** (100-150 LOC): DataTable, FormBuilder, Dashboard
### 3. Custom Hooks
Extract business logic into reusable hooks:
- **Data Management**: useCRUD, useSearch, useSort, useJSONData
- **UI State**: useDialog, useActionExecutor
- **Forms**: useForm, useFormField
## Quick Start
### Define a Page Schema
```typescript
import { PageSchema } from '@/types/json-ui'
export const myPageSchema: PageSchema = {
id: 'my-page',
name: 'My Page',
layout: { type: 'single' },
// Data sources
dataSources: [
{
id: 'items',
type: 'kv', // Persisted to KV store
key: 'my-items',
defaultValue: []
},
{
id: 'stats',
type: 'computed', // Computed from other data
compute: (data) => ({
total: data.items?.length || 0
}),
dependencies: ['items']
}
],
// UI components
components: [
{
id: 'root',
type: 'div',
props: { className: 'p-6' },
children: [
{
id: 'title',
type: 'h1',
props: {
className: 'text-3xl font-bold',
children: 'My Page'
}
},
{
id: 'add-button',
type: 'Button',
props: { children: 'Add Item' },
events: [
{
event: 'click',
actions: [
{
id: 'create-item',
type: 'create',
target: 'items',
compute: (data) => ({
id: Date.now(),
name: 'New Item'
})
},
{
id: 'show-toast',
type: 'show-toast',
message: 'Item added!',
variant: 'success'
}
]
}
]
}
]
}
]
}
```
### Use Custom Hooks
```typescript
import { useCRUD, useSearch, useDialog } from '@/hooks'
function MyComponent() {
// CRUD operations with persistence
const { items, create, update, remove } = useCRUD({
key: 'my-items',
defaultValue: [],
persist: true
})
// Search functionality
const { query, setQuery, filtered } = useSearch({
items,
searchFields: ['name', 'description']
})
// Dialog state management
const dialog = useDialog()
return (
setQuery(e.target.value)} />
{filtered.map(item => (
{item.name}
))}
)
}
```
### Build Atomic Components
```typescript
// Atom: Single responsibility, no business logic
export function ActionButton({ icon, label, onClick }: Props) {
return (
)
}
// Molecule: Composition of atoms
export function SearchBar({ value, onChange, onClear }: Props) {
return (
)
}
```
## Data Source Types
### KV (Persistent)
```typescript
{
id: 'todos',
type: 'kv',
key: 'app-todos',
defaultValue: []
}
```
Data persists between sessions using the KV store.
### Static (Session Only)
```typescript
{
id: 'searchQuery',
type: 'static',
defaultValue: ''
}
```
Data lives only in memory, reset on page reload.
### Computed (Derived)
```typescript
{
id: 'stats',
type: 'computed',
compute: (data) => ({
total: data.todos.length,
completed: data.todos.filter(t => t.completed).length
}),
dependencies: ['todos']
}
```
Automatically recomputes when dependencies change.
## Action Types
### CRUD Actions
**Create**: Add new items
```typescript
{
type: 'create',
target: 'todos',
compute: (data) => ({
id: Date.now(),
text: data.newTodo
})
}
```
**Update**: Modify existing data
```typescript
{
type: 'update',
target: 'todos',
compute: (data) =>
data.todos.map(t =>
t.id === selectedId ? { ...t, completed: true } : t
)
}
```
**Delete**: Remove items
```typescript
{
type: 'delete',
target: 'todos',
path: 'id',
value: todoId
}
```
### UI Actions
**Show Toast**
```typescript
{
type: 'show-toast',
message: 'Task completed!',
variant: 'success' // success | error | info | warning
}
```
**Navigate**
```typescript
{
type: 'navigate',
path: '/dashboard'
}
```
### Value Actions
**Set Value**
```typescript
{
type: 'set-value',
target: 'searchQuery',
compute: (data, event) => event.target.value
}
```
**Toggle Value**
```typescript
{
type: 'toggle-value',
target: 'showCompleted'
}
```
**Increment/Decrement**
```typescript
{
type: 'increment',
target: 'counter',
value: 1
}
```
## Component Bindings
Bind component props to data sources:
```typescript
{
id: 'input',
type: 'Input',
bindings: {
value: {
source: 'searchQuery'
},
placeholder: {
source: 'settings',
path: 'inputPlaceholder'
}
}
}
```
With transformations:
```typescript
{
bindings: {
children: {
source: 'count',
transform: (value) => `${value} items`
}
}
}
```
## Event Handling
### Simple Event
```typescript
{
events: [
{
event: 'click',
actions: [{ type: 'show-toast', message: 'Clicked!' }]
}
]
}
```
### Conditional Event
```typescript
{
events: [
{
event: 'click',
condition: (data) => data.searchQuery.length > 0,
actions: [/* ... */]
}
]
}
```
### Multiple Actions
```typescript
{
events: [
{
event: 'click',
actions: [
{ type: 'create', target: 'items', /* ... */ },
{ type: 'set-value', target: 'input', value: '' },
{ type: 'show-toast', message: 'Added!' }
]
}
]
}
```
## Available Custom Hooks
### Data Hooks
#### `useCRUD`
Complete CRUD operations with KV persistence
```typescript
const { items, create, read, update, remove, clear } = useCRUD({
key: 'todos',
defaultValue: [],
persist: true
})
```
#### `useSearch`
Multi-field search with filtering
```typescript
const { query, setQuery, filtered, resultCount } = useSearch({
items: todos,
searchFields: ['text', 'tags'],
caseSensitive: false
})
```
#### `useSort`
Multi-key sorting with direction toggle
```typescript
const { sorted, field, direction, toggleSort } = useSort({
items: todos,
initialField: 'createdAt',
initialDirection: 'desc'
})
```
#### `useJSONData`
Flexible data management with optional persistence
```typescript
const { value, setValue, updatePath, reset } = useJSONData({
key: 'user-prefs',
defaultValue: {},
persist: true
})
```
### UI Hooks
#### `useDialog`
Dialog/modal state management
```typescript
const dialog = useDialog(false)
// dialog.isOpen, dialog.open(), dialog.close(), dialog.toggle()
```
#### `useActionExecutor`
Execute JSON-defined actions
```typescript
const { executeAction, executeActions } = useActionExecutor(context)
await executeAction({ type: 'create', target: 'items', /* ... */ })
```
## Best Practices
### 1. Keep Components Small
- **Atoms**: < 50 LOC
- **Molecules**: 50-100 LOC
- **Organisms**: 100-150 LOC
- If larger, split into smaller pieces
### 2. Extract Logic to Hooks
```typescript
// ❌ Bad: Logic in component
function TodoList() {
const [todos, setTodos] = useState([])
const addTodo = (text) => setTodos([...todos, { id: Date.now(), text }])
const removeTodo = (id) => setTodos(todos.filter(t => t.id !== id))
// ...
}
// ✅ Good: Logic in hook
function TodoList() {
const { items: todos, create, remove } = useCRUD({
key: 'todos',
defaultValue: []
})
// Component only handles UI
}
```
### 3. Use Computed Data Sources
```typescript
// ❌ Bad: Computing in render
components: [{
type: 'div',
props: {
children: `${data.todos.filter(t => t.completed).length} completed`
}
}]
// ✅ Good: Computed data source
dataSources: [
{
id: 'stats',
type: 'computed',
compute: (data) => ({
completed: data.todos.filter(t => t.completed).length
})
}
],
components: [{
bindings: {
children: {
source: 'stats',
path: 'completed',
transform: (v) => `${v} completed`
}
}
}]
```
### 4. Chain Actions
```typescript
// Multiple related actions in sequence
events: [{
event: 'click',
actions: [
{ type: 'create', /* ... */ }, // 1. Add item
{ type: 'set-value', /* ... */ }, // 2. Clear input
{ type: 'show-toast', /* ... */ } // 3. Show feedback
]
}]
```
### 5. Leverage Atomic Composition
```typescript
// Build complex UIs from simple atoms
Dashboard
```
## Example: Complete Todo App
See `/src/schemas/todo-list.json` for a full working example with:
- KV persistence
- Computed statistics
- CRUD operations
- Action chaining
- Conditional rendering
- Event handling
## Migration Strategy
### Phase 1: Extract Hooks
1. Identify repeated logic patterns
2. Create custom hooks
3. Replace inline logic with hook calls
### Phase 2: Break Down Components
1. Identify large components (>150 LOC)
2. Split into atoms, molecules, organisms
3. Compose back together
### Phase 3: Define JSON Schemas
1. Convert simple pages to JSON first
2. Test with PageRenderer
3. Gradually migrate complex pages
## Performance Tips
1. **Use `useMemo` for expensive computations**
2. **Implement virtual scrolling for large lists**
3. **Lazy load heavy components**
4. **Debounce search and filter operations**
5. **Use computed data sources instead of computing in render**
## Troubleshooting
### Data not persisting?
- Check data source type is 'kv'
- Verify key is unique
- Ensure useKV is called unconditionally
### Actions not executing?
- Check action type spelling
- Verify target matches data source id
- Ensure compute function returns correct type
### Components not rendering?
- Check component type is registered
- Verify props match component API
- Check conditional bindings
## Resources
- **Type Definitions**: `/src/types/json-ui.ts`
- **JSON Schemas**: `/src/schemas/*.json`
- **Compute Functions**: `/src/schemas/compute-functions.ts`
- **Schema Loader**: `/src/schemas/schema-loader.ts`
- **Custom Hooks**: `/src/hooks/data/` and `/src/hooks/ui/`
- **Atomic Components**: `/src/components/atoms/`
- **Component Registry**: `/src/lib/json-ui/component-registry.ts`
---
**Built with ❤️ using React, TypeScript, and Shadcn UI**