@skylabs-digital/react-proto-kit
v1.35.0
Published
React prototyping kit for rapid API development - from idea to working prototype in minutes
Maintainers
Readme
React Proto Kit
From idea to working prototype in minutes ⚡
A powerful React prototyping toolkit that eliminates boilerplate and accelerates development. Build full-stack applications with type-safe APIs, real-time state management, and automatic CRUD operations.
🚀 Quick Start
One-liner to get started:
npm install @skylabs-digital/react-proto-kit zod react react-router-domimport { createDomainApi, z } from '@skylabs-digital/react-proto-kit';
// Define your data schema
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(0),
});
// Create a fully functional API
const userApi = createDomainApi('users', userSchema, userSchema);
// Use it in your component
function UserList() {
const { data: users, loading } = userApi.useList();
const { mutate: createUser } = userApi.useCreate();
if (loading) return <div>Loading...</div>;
return (
<div>
<button onClick={() => createUser({ name: 'John', email: '[email protected]', age: 30 })}>
Add User
</button>
{users?.map(user => (
<div key={user.id}>{user.name} - {user.email}</div>
))}
</div>
);
}That's it! You now have a fully functional CRUD API with TypeScript support, optimistic updates, and automatic state management.
✨ Key Features
- 🔥 Zero Boilerplate: One function call creates a complete CRUD API
- 🎯 Type-Safe: Full TypeScript support with automatic type inference
- ⚡ Real-time: Automatic state synchronization across components
- 🔄 Optimistic Updates: Instant UI feedback with automatic rollback on errors
- 🌐 Backend Agnostic: Works with any REST API or local storage
- 📝 Form Handling: Built-in form validation and state management
- 🔗 Nested Resources: Support for complex resource relationships
- 🎨 Builder Pattern: Chainable API for dynamic configurations
- 📊 Query Parameters: Static and dynamic query parameter management
- 🔍 URL State: Automatic URL synchronization for filters and pagination
- 🎭 Data Orchestrator: Aggregate multiple API calls with smart loading states
- ⚡ Auto-Refetch: Watch URL params and automatically refetch data on changes
- ✨ Smooth Transitions: Stale-while-revalidate for flicker-free UX
- 🎨 UI Components: Built-in Modal, Drawer, Tabs, Stepper, Accordion, and Snackbar components with URL state management
📖 Table of Contents
📦 Installation
# npm
npm install @skylabs-digital/react-proto-kit zod react react-router-dom
# yarn
yarn add @skylabs-digital/react-proto-kit zod react react-router-dom
# pnpm
pnpm add @skylabs-digital/react-proto-kit zod react react-router-domPeer Dependencies
react>= 16.8.0react-router-dom>= 6.0.0zod>= 3.0.0
🎯 Basic Usage
1. Setup Providers
Wrap your app with the necessary providers:
import { BrowserRouter } from 'react-router-dom';
import { ApiClientProvider, GlobalStateProvider } from '@skylabs-digital/react-proto-kit';
function App() {
return (
<BrowserRouter>
<ApiClientProvider connectorType="fetch" config={{ baseUrl: 'http://localhost:3001' }}>
<GlobalStateProvider>
{/* Your app components */}
</GlobalStateProvider>
</ApiClientProvider>
</BrowserRouter>
);
}2. Define Your Schema
import { z } from '@skylabs-digital/react-proto-kit';
const todoSchema = z.object({
text: z.string().min(1, 'Todo text is required'),
completed: z.boolean(),
priority: z.enum(['low', 'medium', 'high']).default('medium'),
});3. Create Your API
import { createDomainApi } from '@skylabs-digital/react-proto-kit';
const todoApi = createDomainApi('todos', todoSchema, todoSchema, {
optimistic: true,
cacheTime: 5 * 60 * 1000, // 5 minutes
});4. Use in Components
function TodoApp() {
const { data: todos, loading, error } = todoApi.useList();
const { mutate: createTodo } = todoApi.useCreate();
const { mutate: updateTodo } = todoApi.useUpdate();
const { mutate: deleteTodo } = todoApi.useDelete();
if (loading) return <div>Loading todos...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={() => createTodo({ text: 'New Todo', completed: false })}>
Add Todo
</button>
{todos?.map(todo => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => updateTodo(todo.id, { ...todo, completed: !todo.completed })}>
Toggle
</button>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
</div>
);
}🚀 Advanced Features
Nested Resources
Handle complex resource relationships with ease:
// Comments belong to todos
const commentApi = createDomainApi(
'todos/:todoId/comments',
commentSchema,
commentUpsertSchema,
{
optimistic: false,
queryParams: {
static: { include: 'author' },
dynamic: ['status', 'sortBy']
}
}
);
// Usage with builder pattern
function TodoComments({ todoId }: { todoId: string }) {
const api = commentApi
.withParams({ todoId })
.withQuery({ status: 'published', sortBy: 'createdAt' });
const { data: comments } = api.useList();
const { mutate: createComment } = api.useCreate();
return (
<div>
{comments?.map(comment => (
<div key={comment.id}>{comment.text}</div>
))}
<button onClick={() => createComment({ text: 'New comment', authorId: 'user-1' })}>
Add Comment
</button>
</div>
);
}Different Schemas for Operations
Use different schemas for entity responses vs create/update operations:
// Full entity schema (includes server-generated fields)
const userEntitySchema = z.object({
name: z.string(),
email: z.string().email(),
avatar: z.string().url(), // Server-generated
lastLoginAt: z.string().datetime(), // Server-managed
});
// Schema for create/update operations (excludes server-generated fields)
const userUpsertSchema = z.object({
name: z.string(),
email: z.string().email(),
});
const userApi = createDomainApi('users', userEntitySchema, userUpsertSchema);Type Extraction
Extract TypeScript types from your APIs:
import { ExtractEntityType, ExtractInputType } from '@skylabs-digital/react-proto-kit';
type User = ExtractEntityType<typeof userApi>;
// Result: { id: string; createdAt: string; updatedAt: string; name: string; email: string; avatar: string; lastLoginAt: string; }
type UserInput = ExtractInputType<typeof userApi>;
// Result: { name: string; email: string; }Form Integration
Built-in form handling with validation:
import { useFormData } from '@skylabs-digital/react-proto-kit';
function UserForm() {
const { mutate: createUser } = userApi.useCreate();
const { values, errors, handleInputChange, handleSubmit, reset } = useFormData(
userUpsertSchema,
{ name: '', email: '' }
);
const onSubmit = handleSubmit(async (data) => {
await createUser(data);
reset();
});
return (
<form onSubmit={onSubmit}>
<input
name="name"
value={values.name || ''}
onChange={handleInputChange}
placeholder="Name"
/>
{errors.name && <span>{errors.name}</span>}
<input
name="email"
value={values.email || ''}
onChange={handleInputChange}
placeholder="Email"
/>
{errors.email && <span>{errors.email}</span>}
<button type="submit">Create User</button>
</form>
);
}URL State Management
Synchronize component state with URL parameters:
import { useUrlSelector } from '@skylabs-digital/react-proto-kit';
function TodoList() {
const [filter, setFilter] = useUrlSelector('filter', (value: string) => value as FilterType);
const [page, setPage] = useUrlSelector('page', (value: string) => parseInt(value) || 1);
const { data: todos } = todoApi.useList({
page,
limit: 10,
filter: filter || 'all'
});
return (
<div>
<button onClick={() => setFilter('active')}>Show Active</button>
<button onClick={() => setFilter('completed')}>Show Completed</button>
<button onClick={() => setPage(page + 1)}>Next Page</button>
{/* Render todos */}
</div>
);
}Partial Updates with PATCH
Use PATCH for efficient partial updates:
function TodoItem({ todo }: { todo: Todo }) {
const { mutate: patchTodo } = todoApi.usePatch();
// Only update the completed field
const toggleCompleted = () => {
patchTodo(todo.id, { completed: !todo.completed });
};
return (
<div>
<span>{todo.text}</span>
<button onClick={toggleCompleted}>
{todo.completed ? 'Mark Incomplete' : 'Mark Complete'}
</button>
</div>
);
}Single Record APIs ⭐ NEW
For endpoints that return a single record instead of a list (settings, config, stats):
import { createSingleRecordApi, createSingleRecordReadOnlyApi } from '@skylabs-digital/react-proto-kit';
// Full CRUD for single record (settings, profile, etc.)
const settingsApi = createSingleRecordApi(
'users/:userId/settings',
settingsSchema,
settingsInputSchema,
{
allowReset: true, // Enable useReset() for reset to defaults
refetchInterval: 30000 // Auto-refresh every 30 seconds
}
);
// Read-only for computed/aggregate data (stats, analytics)
const statsApi = createSingleRecordReadOnlyApi(
'dashboard/stats',
statsSchema,
{ refetchInterval: 60000 }
);Usage in components:
function UserSettings({ userId }: { userId: string }) {
const api = settingsApi.withParams({ userId });
// Fetch single record (not a list)
const { data: settings, loading, refetch } = api.useRecord();
// Update entire record (PUT - no ID needed)
const { mutate: updateSettings, loading: updating } = api.useUpdate();
// Partial update (PATCH - no ID needed)
const { mutate: patchSettings } = api.usePatch();
// Reset to defaults (DELETE - optional, requires allowReset: true)
const { mutate: resetSettings } = api.useReset();
const handleSave = async (newSettings: SettingsInput) => {
await updateSettings(newSettings);
};
const toggleDarkMode = async () => {
await patchSettings({ darkMode: !settings?.darkMode });
};
if (loading) return <Spinner />;
return (
<SettingsForm
settings={settings}
onSave={handleSave}
onToggleDarkMode={toggleDarkMode}
onReset={resetSettings}
/>
);
}
// Read-only dashboard stats
function DashboardStats() {
const { data: stats, loading } = statsApi.useRecord();
if (loading) return <StatsSkeleton />;
return (
<div>
<StatCard title="Total Users" value={stats?.totalUsers} />
<StatCard title="Active Today" value={stats?.activeToday} />
</div>
);
}Key differences from createDomainApi:
| Feature | createDomainApi | createSingleRecordApi | createSingleRecordReadOnlyApi |
|---------|-------------------|-------------------------|--------------------------------|
| useList | ✅ | ❌ | ❌ |
| useById | ✅ | ❌ | ❌ |
| useRecord | ❌ | ✅ | ✅ |
| useCreate | ✅ | ❌ | ❌ |
| useUpdate | ✅ (with ID) | ✅ (no ID) | ❌ |
| usePatch | ✅ (with ID) | ✅ (no ID) | ❌ |
| useDelete | ✅ | ❌ | ❌ |
| useReset | ❌ | ✅ (optional) | ❌ |
| refetchInterval | ❌ | ✅ | ✅ |
Data Orchestrator
Manage multiple API calls in a single component with smart loading states. Choose between Hook (flexible) or HOC (declarative) patterns:
Hook Pattern
import { useDataOrchestrator } from '@skylabs-digital/react-proto-kit';
function Dashboard() {
const { data, isLoading, isFetching, hasErrors, errors, retryAll } = useDataOrchestrator({
required: {
users: userApi.useList,
products: productApi.useList,
},
optional: {
stats: statsApi.useQuery,
},
});
if (isLoading) return <FullPageLoader />;
if (hasErrors) return <ErrorPage errors={errors} onRetry={retryAll} />;
return (
<div>
{isFetching && <TopBarSpinner />}
<h1>Users: {data.users!.length}</h1>
<h1>Products: {data.products!.length}</h1>
</div>
);
}HOC Pattern (with Refetch)
import { withDataOrchestrator } from '@skylabs-digital/react-proto-kit';
interface DashboardData {
users: User[];
products: Product[];
}
function DashboardContent({ users, products, orchestrator }: DashboardData & { orchestrator: any }) {
return (
<div>
{/* Refresh all data */}
<button onClick={orchestrator.retryAll} disabled={orchestrator.isFetching}>
{orchestrator.isFetching ? 'Refreshing...' : 'Refresh All'}
</button>
<h1>Users: {users.length}</h1>
{/* Refresh individual resource */}
<button onClick={() => orchestrator.retry('products')}>Refresh Products</button>
{orchestrator.loading.products && <Spinner />}
<h1>Products: {products.length}</h1>
</div>
);
}
export const Dashboard = withDataOrchestrator<DashboardData>(DashboardContent, {
hooks: {
users: userApi.useList,
products: productApi.useList,
}
});URL-Driven Data with Auto-Refetch ⭐ NEW
Perfect for tabs, filters, and pagination driven by URL parameters:
import { withDataOrchestrator, useUrlTabs, useUrlParam } from '@skylabs-digital/react-proto-kit';
interface TodoListData {
todos: Todo[];
}
function TodoListContent({ todos, orchestrator }: TodoListData & { orchestrator: any }) {
const [activeTab, setTab] = useUrlTabs('status', ['active', 'completed', 'archived'], 'active');
return (
<div>
{/* Tab navigation updates URL */}
<button onClick={() => setTab('active')}>Active</button>
<button onClick={() => setTab('completed')}>Completed</button>
<button onClick={() => setTab('archived')}>Archived</button>
{/* Non-blocking refetch indicator */}
{orchestrator.isFetching && <span>🔄 Refreshing...</span>}
{/* List updates automatically when tab changes */}
<ul>
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
</div>
);
}
const TodoListWithData = withDataOrchestrator<TodoListData>(TodoListContent, {
hooks: {
todos: () => {
const [status] = useUrlParam('status'); // Reads ?status=active
return todoApi.withQuery({ status: status || 'active' }).useList();
},
},
options: {
watchSearchParams: ['status'], // Auto-refetch when ?status= changes
refetchBehavior: 'stale-while-revalidate', // Smooth transitions (default)
},
});How it works:
- User clicks "Completed" tab → URL updates to
?status=completed watchSearchParamsdetects change- Hook re-executes with new status value
stale-while-revalidateshows "Active" todos while loading "Completed"- Smooth transition when new data arrives
Key Features:
isLoading: Blocks rendering during first load of required resourcesisFetching: Shows non-blocking indicator for refetcheswatchSearchParams: Auto-refetch when specified URL params change ⭐ NEWrefetchBehavior:'stale-while-revalidate'(smooth) or'blocking'(explicit) ⭐ NEW- Required vs Optional: Control which resources block rendering
- Granular Retry: Retry individual resources or all at once
- Orchestrator Prop: HOC injects refetch capabilities automatically
- Type-Safe: Full TypeScript inference for all data
Refetch Behaviors:
stale-while-revalidate(default): Shows previous data while fetching new data. Perfect for tabs, filters, pagination.blocking: Clears data and shows loading state. Use for critical updates where stale data is misleading.
See Data Orchestrator Documentation for complete examples.
Local Storage Mode
Perfect for prototyping without a backend:
function App() {
return (
<BrowserRouter>
<ApiClientProvider connectorType="localStorage">
<GlobalStateProvider>
{/* Your app works exactly the same! */}
</GlobalStateProvider>
</ApiClientProvider>
</BrowserRouter>
);
}📚 API Reference
createDomainApi(path, entitySchema, upsertSchema, config?)
Creates a complete CRUD API for a resource.
Parameters:
path: string- Resource path (e.g., 'users', 'todos/:todoId/comments')entitySchema: ZodSchema- Schema for entity responsesupsertSchema: ZodSchema- Schema for create/update operationsconfig?: object- Optional configuration
Config Options:
{
optimistic?: boolean; // Enable optimistic updates (default: true)
cacheTime?: number; // Cache duration in milliseconds
queryParams?: {
static?: Record<string, any>; // Always included parameters
dynamic?: string[]; // Runtime configurable parameters
};
}Returns: API object with methods:
useList(params?)- Fetch list of entitiesuseQuery(id)/useById(id)- Fetch single entityuseCreate()- Create new entityuseUpdate()- Update entire entity (PUT)usePatch()- Partial update (PATCH)useDelete()- Delete entitywithParams(params)- Inject path parameters (builder pattern)withQuery(params)- Inject query parameters (builder pattern)
createSingleRecordApi(path, entitySchema, upsertSchema, config?)
Creates an API for single-record endpoints (settings, config, profile).
Parameters:
path: string- Resource path (e.g., 'settings', 'users/:userId/profile')entitySchema: ZodSchema- Schema for entity responsesupsertSchema: ZodSchema- Schema for update operationsconfig?: object- Optional configuration
Config Options:
{
cacheTime?: number; // Cache duration in milliseconds
refetchInterval?: number; // Auto-refetch interval (for real-time data)
allowReset?: boolean; // Enable useReset() method
queryParams?: {
static?: Record<string, any>;
dynamic?: string[];
};
}Returns: API object with methods:
useRecord()- Fetch single recorduseUpdate()- Update entire record (PUT)usePatch()- Partial update (PATCH)useReset()- Reset to defaults (DELETE) - only ifallowReset: truewithParams(params)- Inject path parameterswithQuery(params)- Inject query parameters
createSingleRecordReadOnlyApi(path, entitySchema, config?)
Creates a read-only API for computed/aggregate endpoints (stats, analytics).
Parameters:
path: string- Resource path (e.g., 'dashboard/stats')entitySchema: ZodSchema- Schema for entity responsesconfig?: object- Optional configuration (same as above, minusallowReset)
Returns: API object with methods:
useRecord()- Fetch single recordwithParams(params)- Inject path parameterswithQuery(params)- Inject query parameters
Hooks
All hooks return objects with consistent interfaces:
Query Hooks (useList, useQuery, useById):
{
data: T | T[] | null;
loading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}Mutation Hooks (useCreate, useUpdate, usePatch, useDelete):
{
mutate: (data: T, id?: string) => Promise<T>;
loading: boolean;
error: Error | null;
}Type Utilities
ExtractEntityType<T>- Extract complete entity type with auto-generated fieldsExtractInputType<T>- Extract input type for create/update operations
📁 Examples
The repository includes comprehensive examples:
- Basic Todo - Simple CRUD operations
- Todo with Global State - Real-time state sync
- Todo with Backend - Full-stack integration
- Blog Example - Complex nested resources
- Advanced Patterns - Advanced usage patterns
Run examples locally:
git clone https://github.com/skylabs-digital/react-proto-kit.git
cd react-proto-kit/examples/todo-with-backend
npm install
npm run dev🎨 UI Components
Snackbar Notifications
Built-in toast-style notifications with auto-dismiss and queue management:
import { SnackbarProvider, SnackbarContainer, useSnackbar } from '@skylabs-digital/react-proto-kit';
// Setup (once in your app)
function App() {
return (
<SnackbarProvider>
<SnackbarContainer position="top-right" maxVisible={3} />
<YourApp />
</SnackbarProvider>
);
}
// Use in any component
function SaveButton() {
const { showSnackbar } = useSnackbar();
const handleSave = async () => {
try {
await saveData();
showSnackbar({
message: 'Changes saved successfully!',
variant: 'success',
duration: 3000
});
} catch (error) {
showSnackbar({
message: 'Error saving changes',
variant: 'error',
duration: 5000
});
}
};
return <button onClick={handleSave}>Save</button>;
}Snackbar Features:
- ✅ 4 variants:
success,error,warning,info - ✅ Auto-dismiss with configurable timeout
- ✅ Queue system for multiple notifications
- ✅ Optional action buttons (undo, etc.)
- ✅ Fully customizable via
SnackbarComponentprop - ✅ 6 position options (top/bottom, left/center/right)
- ✅ Portal rendering for proper z-index
Custom Snackbar Component:
import { SnackbarItemProps } from '@skylabs-digital/react-proto-kit';
function CustomSnackbar({ snackbar, onClose, animate }: SnackbarItemProps) {
return (
<div style={{ /* your custom styles */ }}>
<span>{snackbar.message}</span>
{snackbar.action && (
<button onClick={() => {
snackbar.action.onClick();
onClose(snackbar.id);
}}>
{snackbar.action.label}
</button>
)}
<button onClick={() => onClose(snackbar.id)}>×</button>
</div>
);
}
// Use custom component
<SnackbarContainer SnackbarComponent={CustomSnackbar} />Integration with CRUD APIs:
const todosApi = createDomainApi('todos', todoSchema);
const { showSnackbar } = useSnackbar();
const createMutation = todosApi.useCreate({
onSuccess: () => showSnackbar({ message: 'Todo created!', variant: 'success' }),
onError: (e) => showSnackbar({ message: e.message, variant: 'error' })
});📖 Documentation
Comprehensive documentation is available in the docs/ directory:
- API Reference - Complete API documentation with all hooks and components
- UI Components Guide - Complete guide with examples for Modal, Drawer, Tabs, Stepper, Accordion, and Snackbar
- UI Components RFC - Technical design and architecture decisions
- Advanced Usage - Complex patterns and best practices
- Forms Guide - Form handling and validation
- Global Context Guide - State management
- Data Orchestrator - Aggregate multiple API calls
- Architecture - Internal architecture and design decisions
- Migration Guide - Upgrading between versions
🛠 Backend Integration
Express.js Example
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
let todos = [];
let nextId = 1;
// GET /todos
app.get('/todos', (req, res) => {
res.json(todos);
});
// POST /todos
app.post('/todos', (req, res) => {
const todo = {
id: String(nextId++),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...req.body
};
todos.push(todo);
res.status(201).json(todo);
});
// PUT /todos/:id
app.put('/todos/:id', (req, res) => {
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) return res.status(404).json({ error: 'Todo not found' });
todos[index] = {
...todos[index],
...req.body,
updatedAt: new Date().toISOString()
};
res.json(todos[index]);
});
// PATCH /todos/:id
app.patch('/todos/:id', (req, res) => {
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) return res.status(404).json({ error: 'Todo not found' });
todos[index] = {
...todos[index],
...req.body,
updatedAt: new Date().toISOString()
};
res.json(todos[index]);
});
// DELETE /todos/:id
app.delete('/todos/:id', (req, res) => {
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) return res.status(404).json({ error: 'Todo not found' });
todos.splice(index, 1);
res.status(204).send();
});
app.listen(3001, () => {
console.log('Server running on http://localhost:3001');
});🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
git clone https://github.com/skylabs-digital/react-proto-kit.git
cd react-proto-kit
npm install
npm run devRunning Tests
npm test # Run tests once
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run tests with coverage📄 License
MIT © Skylabs Digital
🙏 Acknowledgments
Built with ❤️ by the Skylabs Digital team. Special thanks to:
- Zod for amazing schema validation
- React for the incredible ecosystem
- The open-source community for inspiration and feedback
Ready to prototype at lightning speed? ⚡ Get started now or explore the examples!
