Deco
Full-Code Guides

Building Views

Create custom React interfaces with typed RPC to your backend

Views are custom React interfaces for your tools and workflows. Unlike traditional web apps, there’s no REST API layer, your frontend calls backend tools directly via typed RPC.

Stack

  • React 19 - UI library
  • Tailwind v4 - Utility-first CSS (configured in tailwind.config.js )
  • TanStack Router - Type-safe routing
  • Vite - Fast bundler with hot reload

All configured out of the box in /view .

Calling Tools from React

The RPC client ( src/lib/rpc.ts ) connects your UI to backend tools with full type safety:

 import { client } from "../lib/rpc";
import { useState } from "react";

function CustomerForm() {
  const [status, setStatus] = useState("");
  
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    
    const result = await client.tools.CUSTOMER_CREATE({
      name: formData.get("name") as string,
      email: formData.get("email") as string,
      city: formData.get("city") as string,
      state: formData.get("state") as string,
    });
    
    setStatus(`Customer created with ID: ${result.id}`);
  };
  
  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <input name="city" placeholder="City" required />
      <input name="state" placeholder="State" maxLength={2} required />
      <button type="submit">Create Customer</button>
      {status && <p>{status}</p>}
    </form>
  );
} 

Key benefits:

  • Type safety - TypeScript knows all tool signatures
  • No API layer - Direct function calls
  • Auto-complete - IDE suggests available tools

Run npm run gen:self after adding tools to update types.

Adding Routes

TanStack Router provides type-safe routing. To add a new page:

  1. Create /view/src/routes/my-page.tsx :
 import { createRoute } from "@tanstack/react-router";
import { rootRoute } from "../main";

export const myPageRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "/my-page",
  component: MyPage,
});

function MyPage() {
  return <div>My Page Content</div>;
} 
  1. Register in /view/src/main.tsx :
 import { myPageRoute } from "./routes/my-page";

const routeTree = rootRoute.addChildren([
  indexRoute,
  myPageRoute, // Add your route
]); 
  1. Navigate using Link :
 import { Link } from "@tanstack/react-router";

<Link to="/my-page">Go to My Page</Link> 

Styling with Tailwind

Tailwind v4 is configured by default. Use utility classes:

 <div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-md">
  <h1 className="text-2xl font-bold text-gray-900">Title</h1>
  <p className="text-gray-600">Description text</p>
  <button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
    Click me
  </button>
</div> 

Use src/components/ui/ for reusable components (buttons, forms, etc.). You can integrate any React design system or component library as needed.

Theme Tokens

Views automatically inherit your workspace theme through CSS custom properties. Instead of hardcoding colors, use theme tokens for consistent branding:

Key tokens:

 --background          /* Page background */
--foreground          /* Primary text color */
--card                /* Card/panel backgrounds */
--primary             /* Primary brand color (CTAs) */
--destructive         /* Delete/error states */
--success             /* Success/confirmation states */
--warning             /* Warning/caution states */
--border              /* Borders and dividers */
--radius              /* Border radius */ 

Usage example:

 <button 
  className="px-4 py-2 rounded-[var(--radius)]"
  style={{
    backgroundColor: 'var(--primary)',
    color: 'var(--primary-foreground)'
  }}
>
  Primary Action
</button>

<div 
  className="p-4 border"
  style={{
    backgroundColor: 'var(--card)',
    borderColor: 'var(--border)',
    borderRadius: 'var(--radius)'
  }}
>
  Card content
</div> 

Semantic tokens for states:

 {/* Success state */}
<span style={{
  backgroundColor: 'var(--success)',
  color: 'var(--success-foreground)'
}}>
  Active
</span>

{/* Warning state */}
<span style={{
  backgroundColor: 'var(--warning)',
  color: 'var(--warning-foreground)'
}}>
  Pending
</span>

{/* Destructive state */}
<button style={{
  backgroundColor: 'var(--destructive)',
  color: 'var(--destructive-foreground)'
}}>
  Delete
</button> 

Why use theme tokens?

  • Views automatically match workspace branding
  • Theme changes don’t require code updates
  • Consistent design system across all views
  • Semantic meaning (primary vs destructive) built in

Development Workflow

Hot reload for frontend:

 npm run dev 

Edit React components β†’ instant browser update (no refresh needed).

Backend changes: Edit tools/workflows β†’ Worker restarts β†’ refresh page to see changes.

Type updates: After adding/modifying tools:

 npm run gen:self 

Your client gets updated with new methods and types.

TypeScript prevents errors: If you call a tool that doesn’t exist, the compiler catches it before runtime.

Common Patterns

Loading states:

 const [loading, setLoading] = useState(false);

const handleAction = async () => {
  setLoading(true);
  try {
    await client.tools.MY_TOOL(data);
  } finally {
    setLoading(false);
  }
}; 

Error handling:

 try {
  await client.tools.MY_TOOL(data);
} catch (error) {
  console.error("Tool failed:", error);
  setError(error.message);
} 

Optimistic updates:

 // Update UI immediately
setItems([...items, newItem]);

// Sync to backend
client.tools.ITEM_CREATE(newItem)
  .catch(() => {
    // Revert if failed
    setItems(items);
  }); 

State Management

For simple apps, React’s built-in useState and useEffect are sufficient. For complex state needs, you can add:

  • React Context - Built-in, good for simple global state
  • Zustand - Lightweight state management library
  • React Query - For server state and caching

Since the RPC client returns promises, you can use React Query for automatic caching and refetching:

 import { useQuery } from "@tanstack/react-query";

const { data, isLoading } = useQuery({
  queryKey: ["customers"],
  queryFn: () => client.tools.CUSTOMERS_LIST(),
}); 

The template doesn’t include global state management by default. Add it only if your app needs it.

Remember: Views and tools share the same types. No API contracts, no integration code, just TypeScript.

Found an error or want to improve this page?

Edit this page