Unified React CRUD components library for tables, forms and workflows.
npm install @zango-core/crudTableContext for centralized state managementTableProvider component with all business logicuseTableContext hook for component consumptiontypescript
interface TableContextValue {
// Data Management
data: any[];
isLoading: boolean;
isFetching: boolean;
error: Error | null;
totalCount: number;
// Search State
searchTerm: string;
setSearchTerm: (term: string) => void;
debouncedSearchTerm: string;
// Filter State
filterObj: Record;
setFilterObj: (filters: Record) => void;
debouncedFilterObj: Record;
activeFilterCount: number;
clearFilters: () => void;
// Pagination State
currentPage: number;
setCurrentPage: (page: number) => void;
totalPages: number;
rowsPerPage: number;
setRowsPerPage: (size: number) => void;
// Table Instance (for sorting, columns)
table: Table | null;
columns: ColumnDef[];
// Selection State
selectedRows: any[];
setSelectedRows: (rows: any[]) => void;
selectRow: (row: any) => void;
selectAll: () => void;
clearSelection: () => void;
// View State (for column management)
columnVisibility: Record;
setColumnVisibility: (visibility: Record) => void;
columnOrder: string[];
setColumnOrder: (order: string[]) => void;
columnPinning: ColumnPinningState;
setColumnPinning: (pinning: ColumnPinningState) => void;
sorting: SortingState;
setSorting: (sorting: SortingState) => void;
// Actions
refresh: () => void;
refetch: () => void;
invalidateCache: () => void;
// Configuration
apiUrl: string;
tableId: string;
config: TableConfig;
features: TableFeatures;
}
`State Synchronization Flow
`
User Interaction → Context State Update → Data Refetch → All Components Re-render
↓
Filter Change → filterObj changes → useTableData hook → All Views Update
Search Change → searchTerm changes → API call → Cards/Table/List Update
Page Change → currentPage changes → New data fetch → All Components Update
`Library Exports (50+ Items)
$3
- TableProvider - Main provider component
- useTableContext - Hook to consume context
- TableContext - Raw context (rarely used directly)
- TableContextValue - TypeScript interface$3
- useTableData - Main data management hook
- useTableSearch - Search state and actions
- useTableFilters - Filter state and actions
- useTablePagination - Pagination state and actions
- useTableSelection - Row selection management
- useTableColumns - Column visibility/ordering
- useTableSorting - Sorting state management
- useTableActions - Action methods (refresh, etc.)
- useTableEvents - Event system integration
- useTablePersistence - State persistence
- useTableConfig - Configuration management
- useTableMetadata - Table metadata access
- useTableQuery - Direct query access
- useTableCache - Cache management
- useTableOptimisticUpdates - Optimistic UI updates$3
- TableLayout - Default complete layout
- CompactLayout - Minimal space layout
- SplitLayout - Side-by-side layout
- CustomLayout - Render prop based
- GridLayout - Card/grid optimized layout
- ListLayout - List view optimized
- DashboardLayout - Dashboard style
- MobileLayout - Mobile optimized$3
- TableSearch - Search input component
- TableFilters - Filter panel component
- TablePagination - Pagination controls
- TableUtilities - Action button bar
- TableHeader - Header section
- TableViewOptions - Column/view options
- TableBulkActions - Bulk operation controls
- TableExportButton - Export functionality
- TableRefreshButton - Refresh data button
- TableRowSelector - Row selection checkbox
- TableSortButton - Column sort control
- TableFilterChips - Active filter display
- TableEmptyState - No data state
- TableLoadingState - Loading indicator
- TableErrorState - Error handling display$3
- TableData - Data render prop
- TableControls - Controls render prop
- TableView - View render prop
- TableState - State render prop
- TableActions - Actions render prop$3
- buildApiUrl - URL construction helper
- formatTableData - Data formatting
- validateFilters - Filter validation
- createTableColumns - Column generation
- handleTableEvents - Event handling
- serializeTableState - State serialization
- parseTableConfig - Configuration parsing
- optimizeTableQuery - Query optimization
- cacheTableData - Data caching
- debugTableState - Development debuggingConsumer Usage Examples
$3
`typescript
// Updated to use new CrudHandler
function CustomerPage() {
return ;
}
`$3
`typescript
function CustomerCardPage() {
return (
{/ Use default search component /}
{/ Use default filter component /}
{/ Custom card view using context data /}
{/ Use default pagination /}
);
}function CustomCardGrid() {
const { data, isLoading } = useTableContext();
if (isLoading) return
Loading cards...;
return (
{data.map(customer => (
))}
);
}
`$3
`typescript
function ProductDashboard() {
return (
{/ Custom sidebar with default filter logic /}
{/ Custom header /}
Products
{/ Custom kanban view /}
{/ Default pagination /}
);
}
`$3
`typescript
function CustomProductManager() {
const {
data,
isLoading,
searchTerm,
setSearchTerm,
filterObj,
setFilterObj,
currentPage,
setCurrentPage,
totalPages,
refresh
} = useTableData({ apiUrl: '/api/products' });
return (
{/ Completely custom search /}
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search products..."
className="custom-search"
/>
{/ Custom filter buttons /}
onClick={() => setFilterObj({...filterObj, category: 'electronics'})}
className={filterObj.category === 'electronics' ? 'active' : ''}
>
Electronics
onClick={() => setFilterObj({...filterObj, category: 'clothing'})}
className={filterObj.category === 'clothing' ? 'active' : ''}
>
Clothing
{/ Custom grid view /}
{data.map(product => (
))}
{/ Custom pagination /}
disabled={currentPage === 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
Previous
Page {currentPage} of {totalPages}
disabled={currentPage === totalPages}
onClick={() => setCurrentPage(currentPage + 1)}
>
Next
);
}
`$3
`typescript
function OrderManagement() {
return (
{({ data, isLoading, filters, pagination, actions }) => (
{/ Custom controls using render prop data /}
filters={filters}
onRefresh={actions.refresh}
/>
{/ Custom view using data /}
orders={data}
loading={isLoading}
/>
{/ Custom pagination using pagination data /}
current={pagination.currentPage}
total={pagination.totalPages}
onChange={pagination.setCurrentPage}
/>
)}
);
}
`$3
`typescript
function AdvancedCustomerFilters() {
const { filterObj, setFilterObj, activeFilterCount, clearFilters } = useTableFilters();
const handleLocationFilter = (location) => {
setFilterObj({
...filterObj,
location: location
});
};
const handleDateRangeFilter = (startDate, endDate) => {
setFilterObj({
...filterObj,
dateRange: [startDate, endDate]
});
};
return (
{/ Custom location filter /}
value={filterObj.location || ''}
onChange={(e) => handleLocationFilter(e.target.value)}
>
{/ Custom date range filter /}
onDateChange={handleDateRangeFilter}
value={filterObj.dateRange}
/>
{/ Filter status and clear /}
{activeFilterCount > 0 && (
<>
{activeFilterCount} filters active
>
)}
);
}
`$3
`typescript
function MobileCustomerList() {
return (
{/ Mobile-friendly search /}
{/ Collapsible filters /}
{/ List view for mobile /}
{/ Infinite scroll pagination /}
);
}
`$3
`typescript
function CustomerDetailSplit() {
const [selectedCustomer, setSelectedCustomer] = useState(null);
return (
{/ Left panel - List /}
onCustomerSelect={setSelectedCustomer}
selectedId={selectedCustomer?.id}
/>
{/ Right panel - Detail /}
{selectedCustomer ? (
) : (
Select a customer to view details
)}
);
}
`Implementation Files to Create
$3
`
src/table/context/
├── TableContext.tsx # Context definition
├── TableProvider.tsx # Provider with all logic
├── useTableContext.tsx # Context consumer hook
└── index.ts # Context exportssrc/table/hooks/
├── useTableData.tsx # Main data hook
├── useTableSearch.tsx # Search management
├── useTableFilters.tsx # Filter management
├── useTablePagination.tsx # Pagination management
├── useTableSelection.tsx # Selection management
└── [10 more specialized hooks]
src/table/layouts/
├── TableLayout.tsx # Default layout
├── CompactLayout.tsx # Space-efficient
├── SplitLayout.tsx # Side-by-side
├── CustomLayout.tsx # Render props
└── [4 more layout variants]
src/table/render-props/
├── TableData.tsx # Data render prop
├── TableControls.tsx # Controls render prop
├── TableView.tsx # View render prop
└── [2 more render prop components]
`Migration Strategy
$3
- Existing is replaced by
- No breaking changes for current consumers
- Internal refactoring only$3
- Add pattern alongside existing API
- Provide new hooks for custom development
- Document migration benefits$3
- Complete library of composable components
- Comprehensive examples and documentation
- Performance optimizationsKey Benefits
1. Perfect State Synchronization: All components stay in sync automatically
2. Component Agnostic: Mix default and custom components freely
3. Gradual Migration: Adopt new patterns incrementally
4. Type Safety: Full TypeScript support throughout
5. Performance: Optimized re-rendering and API calls
6. Developer Experience: Intuitive APIs with comprehensive examples
Development Timeline
- Week 1: Context system and provider setup
- Week 2: Refactor existing components
- Week 3: New hooks and layout components
- Week 4: Render props and optimization
- Week 5: Documentation and examplesThis architecture enables infinite customization possibilities while maintaining the simplicity of the original API for basic use cases.
---
Array Field Row-Level Controls
The form system now supports row-level button control for array fields, allowing you to control the visibility of action buttons (remove, copy, move up/down) and set read-only state on individual rows within array fields.
Features
- Per-row button visibility: Control which action buttons appear for each row
- Read-only rows: Make specific rows non-editable while keeping others editable
- Backward compatibility: Existing forms continue working unchanged
- Fallback support: Row-level flags take precedence over field-level
ui:optionsRow-Level Flags
Add these flags to individual row data in your array field defaults:
| Flag | Type | Description |
|------|------|-------------|
|
_hasRemove | boolean | Show/hide the remove (trash) button for this row |
| _hasCopy | boolean | Show/hide the copy button for this row |
| _hasMoveUp | boolean | Show/hide the move up (arrow up) button for this row |
| _hasMoveDown | boolean | Show/hide the move down (arrow down) button for this row |
| _isReadOnly | boolean | Make the entire row read-only (disables all form fields and buttons) |Usage Examples
$3
`javascript
{
"functional_team_members": {
"type": "array",
"default": [
{
"department": "local_safety",
"name": "Rajat",
"email": "rajat@zelthy.com",
"_hasRemove": false // This row cannot be removed
},
{
"department": "commercial_marketing",
// No flags = uses field-level settings or defaults
}
]
}
}
`$3
`javascript
{
"business_team_members": {
"type": "array",
"title": "Business Team Members",
"default": [
{
// Read-only system user - cannot be edited or removed
"department": "system_admin",
"name": "System Administrator",
"email": "admin@company.com",
"_isReadOnly": true,
"_hasRemove": false,
"_hasCopy": false,
"_hasMoveUp": false,
"_hasMoveDown": false
},
{
// Required user - can be edited but not removed
"department": "business_reviewer",
"name": "John Doe",
"email": "john@company.com",
"_hasRemove": false,
"_hasCopy": true // Allow copying this template
},
{
// Standard editable row with all controls
"department": "program_owner"
// Uses default field-level settings
}
]
}
}
`$3
`javascript
{
"json_schema": {
"type": "object",
"properties": {
"team_members": {
"type": "array",
"title": "Team Members",
"minItems": 1,
"items": {
"type": "object",
"required": ["name", "email", "role"],
"properties": {
"name": {"type": "string", "title": "Name"},
"email": {"type": "string", "title": "Email", "format": "email"},
"role": {
"type": "string",
"title": "Role",
"enum": ["admin", "user", "viewer"]
}
}
},
"default": [
{
"name": "System Admin",
"email": "admin@company.com",
"role": "admin",
"_isReadOnly": true,
"_hasRemove": false
},
{
"name": "Template User",
"email": "template@company.com",
"role": "user",
"_hasCopy": true,
"_hasRemove": false
}
]
}
}
},
"ui_schema": {
"team_members": {
"ui:options": {
"orderable": true, // Field-level default: allow reordering
"addable": true, // Field-level default: allow adding
"removable": true // Field-level default: allow removing
}
}
}
}
`Precedence Rules
The system follows this precedence order:
1. Row-level flags (highest priority)
2. Field-level
ui:options (fallback)
3. System defaults (lowest priority)`javascript
// Row-level flag overrides field-level setting
{
"ui_schema": {
"members": {
"ui:options": {
"removable": true // Field says "allow remove"
}
}
},
"default": [
{
"name": "John",
"_hasRemove": false // Row-level override: this row cannot be removed
}
]
}
`Read-Only Row Behavior
When
_isReadOnly: true is set on a row:- Visual changes: Row becomes slightly transparent (opacity-75)
- Form fields disabled: All input fields become non-interactive
- Buttons disabled: All action buttons are disabled and grayed out
- Pointer events disabled: No mouse interactions allowed on form fields
`javascript
{
"default": [
{
"name": "Protected User",
"email": "protected@company.com",
"_isReadOnly": true // This entire row cannot be edited
}
]
}
`Data Cleaning
The system automatically removes all internal flags before form submission:
-
_hasRemove, _hasCopy, _hasMoveUp, _hasMoveDown, _isReadOnly are stripped
- Clean data is sent to both custom on_submit handlers and API endpoints
- No manual cleaning requiredMigration Notes
- Existing forms: Continue working unchanged
- No breaking changes: All current functionality preserved
- Gradual adoption: Add row-level flags only where needed
- Field-level fallback: Existing
ui:options still work as beforeImplementation Details
The feature uses:
- Lucide React icons:
ArrowUp, ArrowDown, Copy, Trash`