{/* Grid layout */}
```
---
# Common Patterns
## Form with Input and Textarea
```tsx
function MyForm() {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
return (
);
}
```
## Data List with Loading/Error States
```tsx
function ItemList() {
const { client } = useUserSession();
const { data, isLoading, error, refetch } = useFetch(
() => client.store.collections.list(),
[]
);
if (isLoading) return
;
if (error) return
{error.message};
return (
{data?.map(item => (
{item.name}
))}
);
}
```
## Modal with Form
```tsx
function CreateModal({ open, onClose, onCreated }) {
const [name, setName] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const { client } = useUserSession();
const toast = useToast();
const handleSubmit = async () => {
setIsSubmitting(true);
try {
await client.store.collections.create({ name });
toast({ title: 'Created successfully' });
onCreated();
onClose();
} catch (err) {
toast({ title: 'Error', description: err.message, variant: 'destructive' });
} finally {
setIsSubmitting(false);
}
};
return (
Create Item
);
}
```
---
# Key Differences from Standard React
1. **Input onChange** passes value directly: `onChange={setValue}` not `onChange={(e) => setValue(e.target.value)}`
2. **Textarea onChange** uses standard React events
3. **VModalFooter** uses flex-row-reverse (primary button first in code, appears right)
4. **NestedRouterProvider** for plugin routing (instead of BrowserRouter)
5. **useUserSession** provides pre-authenticated Vertesia client