| name | judo-frontend-hooks-docs |
| description | Frontend hook system documentation. Covers data, UI, table, action, navigation, and validation hooks. |
| disable-model-invocation | false |
| user-invocable | false |
| agent | general-purpose |
Hook System Overview
Introduction
The JUDO frontend uses a hook-based customization system that allows you to extend and modify generated behavior without editing generated code. Hooks are registered through the central application-customizer.tsx file and use the Pandino dependency injection framework.
Core Concepts
What Are Hooks?
Hooks are customization points in the generated code where you can inject your own logic. They allow you to:
- Modify component behavior
- Add custom UI elements
- Filter and transform data
- Control navigation
- Customize validation
- Override default operations
Why Hooks?
Benefits:
- ✅ No generated code edits - All customizations in
src/custom/
- ✅ Upgrade-safe - Regeneration doesn't break customizations
- ✅ Type-safe - Full TypeScript support
- ✅ Modular - Each hook handles specific functionality
- ✅ Maintainable - Clear separation of concerns
Alternative (Not Recommended):
- ❌ Edit generated files → Build fails on checksum mismatch
- ❌ Add to
.generator-ignore → Lose generator updates
- ❌ Template overrides → Complex to maintain
Hook Architecture
Pandino Service Registration
All hooks register through the Pandino dependency injection framework:
import {
ApplicationCustomizer,
BundleContext,
TableRowHighlightingHook,
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY
} from '~/generated';
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
context.registerService<TableRowHighlightingHook>(
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY,
myHighlightingHook,
{ component: 'ServiceCanvassingEventsTable' }
);
}
}
Key Components
Interface Keys:
- String identifiers for each customizable feature
- Example:
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY
- Exported from
~/generated
Service Properties:
- Configuration objects:
{ component, column, page }
- Filter which components the hook applies to
- Example:
{ component: 'ServiceCampaignForm', column: 'name' }
Hook Implementation:
- Must be React hooks for DI support
- Follow TypeScript interfaces
- Return functions or configuration objects
Precedence:
- Page-level hooks override container-level hooks
- More specific hooks override general hooks
Available Hook Categories
1. Data Hooks
Access and manage application data:
usePrincipal - Current user information
useViewData - Page/dialog data access
Learn more →
2. UI Hooks
Customize visual components:
- AppBar extra components
- Hero and logo customization
- Footer text
- Menu manipulation
Learn more →
3. Table Hooks
Enhance table functionality:
- Row highlighting
- Custom columns
- Sidekick components (above-table UI)
- Column customization
Learn more →
4. Action Hooks
Control operations and flows:
- Container actions (form/table operations)
- Page actions (custom button handlers)
- Operation flow management
- Post-operation navigation
Learn more →
5. Navigation and Access
Manage routing and permissions:
- Redirect service
- Access filtering
- Hotkey support
- Navigation interception
Learn more →
6. Validation Hooks
Customize input validation:
- Date/DateTime validation
- Field-specific rules
- Custom error messages
Learn more →
Using .default Template Files
The generator creates .default template files as blueprints for customization.
Understanding .default Files
Important: .default files (.tsx.default, .ts.default) are compiled by the build system.
Two Options:
Option 1: Keep .default Extension (Recommended)
find src/custom -name "*.default"
vim src/custom/application-customizer.tsx.default
Advantages:
- ✅ No file copying needed
- ✅ Generator updates blueprint without overwriting your changes
- ✅ Clear that file originated from template
Option 2: Remove .default Extension
cp src/custom/application-customizer.tsx.default src/custom/application-customizer.tsx
echo "src/custom/application-customizer.tsx" >> .generator-ignore
vim src/custom/application-customizer.tsx
Advantages:
- ✅ Clean filenames
- ✅ Standard convention
Example .default File
Generated src/custom/application-customizer.tsx.default:
import { ApplicationCustomizer, BundleContext } from '~/generated';
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
}
}
After customization:
import {
ApplicationCustomizer,
BundleContext,
TableRowHighlightingHook,
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY
} from '~/generated';
import { canvassingEventHighlighting } from './hooks/tableRowHighlighting';
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
context.registerService<TableRowHighlightingHook>(
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY,
canvassingEventHighlighting,
{ component: 'ServiceCanvassingEventsTable' }
);
}
}
Common Template Patterns
| Pattern | Purpose | Recommendation |
|---|
*-customizer.tsx.default | Component customization entry points | Keep .default |
hooks/*.tsx.default | Custom hook implementations | Either works |
theme/*.ts.default | Theme and styling customizations | Usually rename |
pages/*-hook-registration.tsx.default | Page-level hook registrations | Keep .default |
Complete Workflow Example
Scenario: Add Table Row Highlighting
find src/custom -name "*.default"
vim src/custom/application-customizer.tsx.default
mkdir -p src/custom/hooks
cat > src/custom/hooks/tableRowHighlighting.tsx << 'EOF'
import { TableRowHighlightingHook } from '~/generated';
import { ServiceCanvassingEventStored } from '~/generated/data-api';
export const canvassingEventHighlighting: TableRowHighlightingHook<ServiceCanvassingEventStored> = () => {
return () => ([
{
name: 'overdue',
label: 'Overdue Events',
backgroundColor: '#ffebee',
condition: (params) => {
const deadline = new Date(params.row.eventDate);
return deadline < new Date() && !params.row.completed;
}
}
]);
};
EOF
echo "src/custom/hooks/tableRowHighlighting.tsx" >> .generator-ignore
cd application/frontend-react/northwind__[actor_fqn]
pnpm run dev
Hook Registration Patterns
Single Hook Registration
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
context.registerService<TableRowHighlightingHook>(
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY,
myHighlightingHook,
{ component: 'ServiceCampaignsTable' }
);
}
}
Multiple Hooks
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
context.registerService<TableRowHighlightingHook>(
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY,
campaignHighlighting,
{ component: 'ServiceCampaignsTable' }
);
context.registerService<ColumnCustomizerHook>(
TABLE_COLUMN_CUSTOMIZER_HOOK_INTERFACE_KEY,
statusColumn,
{ component: 'ServiceCampaignsTable', column: 'status' }
);
context.registerService<FC>(
SIDEKICK_COMPONENT_INTERFACE_KEY,
CampaignChartSidekick,
{ component: 'ServiceCampaignsTable' }
);
}
}
Conditional Registration
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
if (process.env.NODE_ENV === 'production') {
context.registerService<AnalyticsHook>(
ANALYTICS_HOOK_INTERFACE_KEY,
analyticsHook
);
}
if (localStorage.getItem('betaFeatures') === 'true') {
context.registerService<BetaFeatureHook>(
BETA_FEATURE_HOOK_INTERFACE_KEY,
betaFeatureHook
);
}
}
}
Understanding .generator-ignore
When to Use
Add to .generator-ignore:
- ✅ Custom files you create from scratch
- ✅ Template files you renamed (removed .default)
- ✅ Generated files you edited directly (not preferred)
Don't add to .generator-ignore:
- ❌ Template files with .default extension
- ❌ Generated files you haven't modified
Example .generator-ignore
# Custom application customizer (if renamed from .default)
src/custom/application-customizer.tsx
# Custom hooks (created from scratch)
src/custom/hooks/tableRowHighlighting.tsx
src/custom/hooks/usePrincipal.tsx
src/custom/hooks/useCustomAuth.tsx
# Custom components
src/custom/components/CampaignChart.tsx
src/custom/components/NotificationBell.tsx
# Theme customizations (if renamed)
src/theme/palette.ts
src/theme/typography.ts
# Directly edited generated files (not recommended)
# src/generated/pages/CustomPage.tsx
Verify Protection
grep "application-customizer.tsx" .generator-ignore
cat .generator-ignore
mvn clean install
git diff src/custom/
Best Practices
1. Prefer Hooks Over Direct Edits
context.registerService<TableRowHighlightingHook>(
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY,
myHook,
{ component: 'ServiceCampaignsTable' }
);
2. Keep .default Extension
vim src/custom/application-customizer.tsx.default
cp src/custom/application-customizer.tsx.default src/custom/application-customizer.tsx
echo "src/custom/application-customizer.tsx" >> .generator-ignore
3. Organize Custom Code
src/custom/
├── application-customizer.tsx.default
├── hooks/
│ ├── tableRowHighlighting.tsx
│ ├── usePrincipal.tsx
│ └── validation.tsx
├── components/
│ ├── CampaignChart.tsx
│ └── NotificationBell.tsx
└── utils/
└── helpers.ts
4. Document Customizations
export const canvassingEventHighlighting: TableRowHighlightingHook = () => {
return () => ([...]);
};
5. Test After Regeneration
mvn clean install
pnpm run dev
Troubleshooting
"Hook not executing"
Check registration:
console.log('Registering hook');
context.registerService<TableRowHighlightingHook>(
TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY,
myHook,
{ component: 'ServiceCampaignsTable' }
);
Verify component name:
grep -r "export const.*Table" src/generated/pages/
"File overwritten after build"
Add to .generator-ignore:
echo "src/custom/hooks/myHook.tsx" >> .generator-ignore
"Type errors with hook interface"
Import correct types:
import { TableRowHighlightingHook } from '~/generated';
import { TableRowHighlightingHook } from '~/generated/hooks';
Related Documentation