Best practices
Follow these best practices to build maintainable, performant, and user-friendly applications with FlowX.AI UI Toolkits. These recommendations are based on real-world implementations and common patterns.Architecture patterns
Component organization
Organize your components by feature rather than by type for better maintainability:src/
├── features/
│ ├── customer-onboarding/
│ │ ├── components/
│ │ │ ├── PersonalInfoForm.tsx
│ │ │ ├── AddressForm.tsx
│ │ │ └── DocumentUpload.tsx
│ │ ├── CustomerOnboarding.tsx
│ │ └── index.ts
│ ├── loan-application/
│ │ ├── components/
│ │ └── LoanApplication.tsx
│ └── account-management/
├── shared/
│ ├── components/
│ ├── hooks/
│ └── utils/
└── App.tsx
Separation of concerns
Keep process logic separate from presentation:- Angular
- React
customer-onboarding.component.ts
// Container component - handles process logic
@Component({
selector: 'app-customer-onboarding',
template: `
<flx-process-container
[processName]="processName"
[processStartData]="startData"
(processCompleted)="handleCompletion($event)"
>
</flx-process-container>
`
})
export class CustomerOnboardingComponent {
processName = 'customer-onboarding';
startData = { source: 'web' };
constructor(private router: Router) {}
handleCompletion(event: any) {
this.router.navigate(['/success'], {
queryParams: { customerId: event.data.customerId }
});
}
}
// Presentation component - handles UI only
@Component({
selector: 'app-personal-info-form',
template: `
<form flxForm>
<flx-input name="firstName" label="First Name" [required]="true"></flx-input>
<flx-input name="lastName" label="Last Name" [required]="true"></flx-input>
<flx-button actionName="NEXT" label="Continue"></flx-button>
</form>
`
})
export class PersonalInfoFormComponent {}
CustomerOnboarding.tsx
// Container component - handles process logic
export function CustomerOnboarding() {
const navigate = useNavigate();
const processName = 'customer-onboarding';
const startData = { source: 'web' };
const handleCompletion = (event: any) => {
navigate(`/success?customerId=${event.data.customerId}`);
};
return (
<ProcessContainer
processName={processName}
processStartData={startData}
onProcessCompleted={handleCompletion}
/>
);
}
// Presentation component - handles UI only
export function PersonalInfoForm() {
return (
<FlxForm>
<FlxInput name="firstName" label="First Name" required />
<FlxInput name="lastName" label="Last Name" required />
<FlxButton actionName="NEXT" label="Continue" />
</FlxForm>
);
}
Performance optimization
Lazy loading processes
Load process containers only when needed:- Angular
- React
app-routing.module.ts
const routes: Routes = [
{
path: 'onboarding',
loadChildren: () => import('./features/customer-onboarding/customer-onboarding.module')
.then(m => m.CustomerOnboardingModule)
},
{
path: 'loan',
loadChildren: () => import('./features/loan-application/loan-application.module')
.then(m => m.LoanApplicationModule)
}
];
App.tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const CustomerOnboarding = lazy(() => import('./features/customer-onboarding/CustomerOnboarding'));
const LoanApplication = lazy(() => import('./features/loan-application/LoanApplication'));
export function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/onboarding" element={<CustomerOnboarding />} />
<Route path="/loan" element={<LoanApplication />} />
</Routes>
</Suspense>
);
}
Minimize re-renders
Use memoization to prevent unnecessary re-renders:- Angular
- React
data-table.component.ts
@Component({
selector: 'app-data-table',
template: `
<flx-table
[name]="tableName"
[columns]="columns"
[pageSize]="pageSize"
></flx-table>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DataTableComponent {
tableName = 'customers';
pageSize = 20;
// Define columns once
columns = [
{ field: 'id', header: 'ID' },
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' }
];
}
DataTable.tsx
import { useMemo } from 'react';
import { FlxTable } from '@flowx/react-ui-toolkit';
export function DataTable() {
const tableName = 'customers';
const pageSize = 20;
// Memoize columns to prevent recreation on each render
const columns = useMemo(() => [
{ field: 'id', header: 'ID' },
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' }
], []);
return (
<FlxTable
name={tableName}
columns={columns}
pageSize={pageSize}
/>
);
}
Error handling
Global error handling
Implement centralized error handling for process operations:- Angular
- React
error-handler.service.ts
@Injectable({ providedIn: 'root' })
export class ProcessErrorHandler {
constructor(
private notificationService: NotificationService,
private logger: LoggerService
) {}
handleProcessError(error: any, context?: string) {
// Log error
this.logger.error('Process error', { error, context });
// Show user-friendly message
if (error.status === 401) {
this.notificationService.error('Your session has expired. Please log in again.');
} else if (error.status === 403) {
this.notificationService.error('You do not have permission to perform this action.');
} else if (error.status >= 500) {
this.notificationService.error('A server error occurred. Please try again later.');
} else {
this.notificationService.error(error.message || 'An unexpected error occurred.');
}
}
}
useProcessErrorHandler.ts
import { useCallback } from 'react';
import { useNotification } from './useNotification';
import { useLogger } from './useLogger';
export function useProcessErrorHandler() {
const { showError } = useNotification();
const { logError } = useLogger();
const handleProcessError = useCallback((error: any, context?: string) => {
// Log error
logError('Process error', { error, context });
// Show user-friendly message
if (error.status === 401) {
showError('Your session has expired. Please log in again.');
} else if (error.status === 403) {
showError('You do not have permission to perform this action.');
} else if (error.status >= 500) {
showError('A server error occurred. Please try again later.');
} else {
showError(error.message || 'An unexpected error occurred.');
}
}, [showError, logError]);
return { handleProcessError };
}
Graceful degradation
Provide fallback UI when processes fail to load:ProcessErrorBoundary.tsx
import React, { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ProcessErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: any) {
console.error('Process error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-container">
<h2>Something went wrong</h2>
<p>We're having trouble loading this process. Please try refreshing the page.</p>
<button onClick={() => window.location.reload()}>Refresh</button>
</div>
);
}
return this.props.children;
}
}
Security best practices
Token management
Never hardcode authentication tokens:// ❌ Bad - hardcoded token
const config = {
authToken: 'sk_live_abc123xyz'
};
// ✅ Good - token from environment
const config = {
authToken: process.env.REACT_APP_FLOWX_AUTH_TOKEN
};
// ✅ Better - token from secure storage
const config = {
authToken: await secureStorage.getToken()
};
Input sanitization
Validate and sanitize user input before sending to processes:function sanitizeInput(value: string): string {
return value
.trim()
.replace(/[<>]/g, '') // Remove potential HTML tags
.substring(0, 1000); // Limit length
}
const startData = {
customerName: sanitizeInput(userInput.name),
email: sanitizeInput(userInput.email).toLowerCase()
};
HTTPS only
Always use HTTPS for API connections in production:const config = {
apiUrl: process.env.NODE_ENV === 'production'
? 'https://api.flowx.example.com'
: 'http://localhost:3000'
};
Accessibility
Keyboard navigation
Ensure all interactive elements are keyboard accessible:// Components include built-in keyboard support
<FlxButton
label="Submit"
actionName="SUBMIT"
// Automatically handles Enter and Space key presses
/>
Screen reader support
Provide descriptive labels and ARIA attributes:<FlxInput
name="email"
label="Email Address"
aria-label="Enter your email address"
aria-describedby="email-hint"
required
/>
<span id="email-hint" className="hint-text">
We'll never share your email with anyone else.
</span>
Focus management
Manage focus for better UX, especially after actions:- Angular
- React
@Component({
template: `
<flx-button
actionName="DELETE"
(actionExecuted)="onDelete()"
>Delete</flx-button>
<div #successMessage tabindex="-1">Item deleted successfully</div>
`
})
export class MyComponent {
@ViewChild('successMessage') successMessage!: ElementRef;
onDelete() {
// Focus the success message for screen readers
setTimeout(() => {
this.successMessage.nativeElement.focus();
}, 100);
}
}
export function MyComponent() {
const successMessageRef = useRef<HTMLDivElement>(null);
const handleDelete = () => {
// Focus the success message for screen readers
setTimeout(() => {
successMessageRef.current?.focus();
}, 100);
};
return (
<>
<FlxButton
actionName="DELETE"
onActionExecuted={handleDelete}
>Delete</FlxButton>
<div ref={successMessageRef} tabIndex={-1}>
Item deleted successfully
</div>
</>
);
}
Testing
Component testing
Test components with mocked process data:- Angular
- React
customer-form.component.spec.ts
describe('CustomerFormComponent', () => {
let component: CustomerFormComponent;
let fixture: ComponentFixture<CustomerFormComponent>;
let mockProcessService: jasmine.SpyObj<FlowxProcessService>;
beforeEach(() => {
mockProcessService = jasmine.createSpyObj('FlowxProcessService', [
'startProcess',
'executeAction'
]);
TestBed.configureTestingModule({
declarations: [CustomerFormComponent],
providers: [
{ provide: FlowxProcessService, useValue: mockProcessService }
]
});
fixture = TestBed.createComponent(CustomerFormComponent);
component = fixture.componentInstance;
});
it('should submit form data', () => {
const formData = { firstName: 'John', lastName: 'Doe' };
mockProcessService.executeAction.and.returnValue(of({ success: true }));
component.submitForm(formData);
expect(mockProcessService.executeAction).toHaveBeenCalledWith(
'SUBMIT',
formData
);
});
});
CustomerForm.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { FlowxProvider } from '@flowx/react-ui-toolkit';
import { CustomerForm } from './CustomerForm';
describe('CustomerForm', () => {
const mockConfig = {
apiUrl: 'http://test.example.com',
processApiPath: '/process',
authToken: 'test-token'
};
it('should render form fields', () => {
render(
<FlowxProvider config={mockConfig}>
<CustomerForm />
</FlowxProvider>
);
expect(screen.getByLabelText('First Name')).toBeInTheDocument();
expect(screen.getByLabelText('Last Name')).toBeInTheDocument();
});
it('should validate required fields', async () => {
render(
<FlowxProvider config={mockConfig}>
<CustomerForm />
</FlowxProvider>
);
const submitButton = screen.getByRole('button', { name: 'Submit' });
fireEvent.click(submitButton);
expect(await screen.findByText('First Name is required')).toBeInTheDocument();
});
});
Integration testing
Test complete process flows end-to-end:e2e/customer-onboarding.spec.ts
describe('Customer Onboarding Process', () => {
it('should complete onboarding flow', () => {
cy.visit('/onboarding');
// Step 1: Personal Information
cy.get('[name="firstName"]').type('John');
cy.get('[name="lastName"]').type('Doe');
cy.get('[name="email"]').type('[email protected]');
cy.contains('Continue').click();
// Step 2: Address
cy.get('[name="street"]').type('123 Main St');
cy.get('[name="city"]').type('New York');
cy.contains('Continue').click();
// Step 3: Confirmation
cy.contains('Submit').click();
// Verify completion
cy.url().should('include', '/success');
cy.contains('Onboarding completed successfully');
});
});
Monitoring and logging
Process analytics
Track process metrics for insights:export class ProcessAnalytics {
trackProcessStart(processName: string, data: any) {
analytics.track('Process Started', {
processName,
timestamp: new Date().toISOString(),
startData: data
});
}
trackProcessComplete(processName: string, duration: number) {
analytics.track('Process Completed', {
processName,
duration,
timestamp: new Date().toISOString()
});
}
trackProcessError(processName: string, error: any) {
analytics.track('Process Error', {
processName,
errorMessage: error.message,
errorCode: error.code,
timestamp: new Date().toISOString()
});
}
}
Performance monitoring
Monitor component performance:import { performance } from 'perf_hooks';
export function measureProcessTime(processName: string) {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 3000) {
console.warn(`Process ${processName} took ${duration}ms`);
}
return duration;
};
}
Documentation
Document your process integrations:/**
* Customer Onboarding Process
*
* Process Name: customer-onboarding
* Version: 1.2.0
*
* Start Data:
* @param {string} source - Traffic source (web, mobile, referral)
* @param {string} [campaignId] - Optional marketing campaign ID
*
* Process Variables:
* - personalInfo: Customer personal information
* - address: Customer address details
* - documents: Uploaded verification documents
*
* Actions:
* - CONTINUE: Proceed to next step
* - SAVE_DRAFT: Save progress and exit
* - CANCEL: Abandon process
* - SUBMIT: Complete onboarding
*
* Completion Data:
* @returns {string} customerId - Newly created customer ID
* @returns {string} accountNumber - Account number assigned
*/
export class CustomerOnboardingProcess {}
Summary checklist
Before deploying to production, verify:
- Components are organized by feature
- Lazy loading is implemented for routes
- Error handling covers all process operations
- Authentication tokens are stored securely
- All API calls use HTTPS
- Components are keyboard accessible
- ARIA labels are present where needed
- Unit tests cover critical functionality
- E2E tests validate complete flows
- Analytics tracking is configured
- Performance monitoring is active
- Process integrations are documented