Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
305225d
feat: set up apm
edwin-joseph-cko Jun 4, 2025
3073cfa
feat: set up api
edwin-joseph-cko Jun 9, 2025
bd0d3a5
fix: handle ts errors
edwin-joseph-cko Jun 25, 2025
e1830d6
v1.1.0
edwin-joseph-cko Jun 25, 2025
2d66a5a
feat: set up buttons
edwin-joseph-cko Jun 10, 2025
fe2590f
feat: move default view into components and add state demo
edwin-joseph-cko Jun 13, 2025
33c8032
v1.1.1
edwin-joseph-cko Jun 25, 2025
079a0a6
feat: set up input, otp, form, and update state management
edwin-joseph-cko Jun 13, 2025
56751a3
feat: add phone input
edwin-joseph-cko Jun 17, 2025
81dafe9
fix: update input blur and api pending loop
edwin-joseph-cko Jun 20, 2025
6df294c
fix: handle ts errors
edwin-joseph-cko Jun 25, 2025
11985f8
v1.1.2
edwin-joseph-cko Jun 25, 2025
64b2ba3
fix: update validation
edwin-joseph-cko Jun 23, 2025
298ea18
feat: add success page
edwin-joseph-cko Jun 25, 2025
41b0688
v1.1.3
edwin-joseph-cko Jun 25, 2025
a304fbd
feat: add select component and hook it up to api
edwin-joseph-cko Jun 24, 2025
6f75e41
fix: move to a virtual dom based ui
edwin-joseph-cko Jun 24, 2025
cab9156
v1.1.4
edwin-joseph-cko Jun 25, 2025
d942fe0
feat: update APM events interface to match mobile implementation
edwin-joseph-cko Jul 8, 2025
3436008
feat: add persistent storage utilities
edwin-joseph-cko Jul 8, 2025
4eb02fa
fix: restructure APM configuration API
edwin-joseph-cko Jul 8, 2025
4030fdf
feat: add layout system
edwin-joseph-cko Jul 8, 2025
dd2c5e5
feat: add instruction components (POCD-355)
edwin-joseph-cko Jul 8, 2025
22b29aa
feat: add checkbox form element component
edwin-joseph-cko Jul 8, 2025
695395b
feat: add copy container grouping for consecutive labeled instructions
edwin-joseph-cko Jul 8, 2025
5a3b02e
fix: prevent spurious OTP field-change events during form submission
edwin-joseph-cko Jul 8, 2025
3b5a57a
feat: add API polling cancellation for payment cancellation
edwin-joseph-cko Jul 8, 2025
db2d6cd
feat: add pending payment view with countdown and user actions
edwin-joseph-cko Jul 8, 2025
02c4262
feat: enhance form elements and UI components
edwin-joseph-cko Jul 8, 2025
3d937ba
feat: update ProcessOut integration and telemetry
edwin-joseph-cko Jul 8, 2025
47d1dc0
docs: add documentation and update examples
edwin-joseph-cko Jul 8, 2025
dc29880
feat: Add StateManager for centralized component state management
edwin-joseph-cko Jul 14, 2025
88c8ad7
feat: Add cancel functionality and new UI components
edwin-joseph-cko Jul 14, 2025
a283dbc
refactor: Improve API types and form field handling
edwin-joseph-cko Jul 14, 2025
a79e3b4
feat: Add polling cancellation and improved state management
edwin-joseph-cko Jul 14, 2025
ff1f209
refactor: Update APM elements to use StateManager
edwin-joseph-cko Jul 14, 2025
4be47c0
refactor: Update APM views and layouts to support new architecture
edwin-joseph-cko Jul 14, 2025
36b674e
refactor: Update core APM infrastructure
edwin-joseph-cko Jul 14, 2025
8ec74c0
docs: Update documentation for new APM architecture
edwin-joseph-cko Jul 14, 2025
94a0018
refactor: Enhance APM views and styling for improved layout and state…
edwin-joseph-cko Jul 14, 2025
1b26515
refactor: Update API types to use type aliases for improved clarity
edwin-joseph-cko Jul 14, 2025
4199557
refactor: Enhance form state management and error handling in NextSte…
edwin-joseph-cko Jul 14, 2025
6f9fcc0
v1.1.5
edwin-joseph-cko Jul 14, 2025
1d7b7bf
refactor: Enhance APM functionality and styling for improved user exp…
edwin-joseph-cko Jul 16, 2025
40d4d34
v1.1.6
edwin-joseph-cko Jul 16, 2025
588bcb4
refactor: Improve redirect handling and error management in APMViewRe…
edwin-joseph-cko Jul 17, 2025
760e2e0
v1.1.7
edwin-joseph-cko Jul 17, 2025
8e4d13b
refactor: Replace random ID generation with a dedicated function for …
edwin-joseph-cko Jul 18, 2025
87b404c
chore: Add comments to clarify the purpose of generateUniqueId function
edwin-joseph-cko Jul 18, 2025
8f8da8e
fix: update fonts fetching to handle incomplete URL substring sanitis…
edwin-joseph-cko Jul 18, 2025
35ea361
chore: Update TypeScript configuration to include polyfills and sourc…
edwin-joseph-cko Jul 18, 2025
e59034f
fix: Enhance font URL validation in Page component
edwin-joseph-cko Jul 21, 2025
fcd9d56
v1.2.0
edwin-joseph-cko Jul 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
393 changes: 393 additions & 0 deletions docs/apm-state-manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,393 @@
# APM StateManager System

## Overview

The StateManager is a global state management system designed specifically for APM (Alternative Payment Methods) components. It provides a centralized way to manage stateful components that need to trigger re-renders when their state changes.

## Problem it Solves

Before StateManager, stateful components in the APM system had several issues:

1. **Global State Pollution**: Components used module-level state variables that were shared between instances
2. **Manual DOM Manipulation**: State changes required manual DOM updates
3. **No Re-render Triggering**: Components couldn't trigger parent view re-renders
4. **Memory Leaks**: State persisted incorrectly across component lifecycles

## Key Features

- **✅ Stable Component IDs**: Uses first-render call order (React hooks pattern)
- **✅ Automatic Re-renders**: State changes trigger parent view updates
- **✅ Batched Updates**: Multiple setState calls in one frame = single re-render
- **✅ Lifecycle Management**: Automatic cleanup when views unmount
- **✅ Memory Efficient**: Prevents memory leaks with proper cleanup
- **✅ Type Safe**: Full TypeScript support with generic state types
- **✅ Position Independent**: Components maintain state when order changes

## Architecture

```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Component A │ │ Component B │ │ Component C │
│ (phone-123) │ │ (otp-456) │ │ (qr-789) │
└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘
│ │ │
└──────────────────────┼──────────────────────┘
┌────────────▼─────────────┐
│ StateManager │
│ (Singleton Instance) │
└────────────┬─────────────┘
┌────────────▼─────────────┐
│ APMViewImpl │
│ (Parent View) │
│ forceUpdate() │
└──────────────────────────┘
```

## Basic Usage



### 1. Use state in your component

```typescript
export const Phone = ({ name, ...props }: PhoneProps) => {
// Simple state management - just pass initial state!
const { state, setState } = useComponentState({
dialing_code: '',
value: '',
iso: '',
});

// Update state triggers automatic re-renders
const handleInputChange = (newValue: string) => {
setState(prevState => ({
...prevState,
value: newValue
}));
};

return input({
value: state.value,
oninput: handleInputChange
});
};
```

### 2. Use Component in View

```typescript
export class MyView extends APMViewImpl {
render() {
return div({ className: 'view' },
Phone({
name: 'user-phone',
dialing_codes: [...]
})
);
}
}
```

## API Reference

### Core Functions

#### `useComponentState<T>(initialState: T)`

Hook for components to use stateful behavior. Automatically generates unique component IDs and detects the current view context.

**Parameters:**
- `initialState`: Initial state object (T)

**Returns:**
```typescript
{
state: T;
setState: (newState: T | ((prevState: T) => T)) => void;
watch: {
(callback: (state: T) => void): () => void;
<K extends keyof T>(field: K, callback: (newValue: T[K]) => void): () => void;
};
}
```

#### `generateComponentId(prefix?)`

Generates a unique component ID.

**Parameters:**
- `prefix`: Optional prefix for the ID (default: 'comp')

**Returns:** Unique string ID

#### `getStateManager()`

Gets the singleton StateManager instance.

**Returns:** StateManager instance

#### `cleanupComponentState(id)`

Manually clean up component state.

**Parameters:**
- `id`: Component ID to clean up

### StateManager Class

#### `registerComponent<T>(id, initialState, view?)`

Register a component with the state manager.

#### `getComponentState<T>(id)`

Get component state by ID.

#### `updateComponentState<T>(id, newState, forceUpdate?)`

Update component state and trigger re-renders.

#### `destroyComponent(id)`

Remove component from state manager.

#### `destroyViewComponents(view)`

Clean up all components associated with a view.

## Migration Guide

### From Global State to StateManager

**Before:**
```typescript
// Global state (problematic)
let phoneState = {
dialing_code: '',
value: '',
iso: ''
};

export const Phone = (props) => {
const handleChange = (newValue) => {
phoneState.value = newValue;
// Manual DOM manipulation required
updatePhoneDisplay();
};

return input({ oninput: handleChange });
};
```

**After:**
```typescript
// StateManager (proper state management)
export const Phone = (props) => {
const { state, setState } = useComponentState({
dialing_code: '',
value: '',
iso: ''
});

const handleChange = (newValue) => {
setState(prevState => ({ ...prevState, value: newValue }));
// Automatic re-render triggered
};

return input({
value: state.value,
oninput: handleChange
});
};
```

### From Module-level State Stores

**Before:**
```typescript
// Module-level state store
const qrStateStore: Record<string, QRState> = {};

export const QR = ({ id, ...props }) => {
if (!qrStateStore[id]) {
qrStateStore[id] = { isDownloading: false };
}

const state = qrStateStore[id];

// Manual updates
const handleDownload = () => {
state.isDownloading = true;
updateQRDisplay(); // Manual DOM manipulation
};
};
```

**After:**
```typescript
// StateManager
export const QR = ({ id, ...props }) => {
const { state, setState } = useComponentState({
isDownloading: false
});

const handleDownload = () => {
setState(prevState => ({ ...prevState, isDownloading: true }));
// Automatic re-render triggered
};
};
```

## Best Practices

### 1. Use the Simple API

```typescript
// ✅ Good - simple and automatic
const { state, setState } = useComponentState({ count: 0 });

// Component IDs are automatically generated and stable across re-renders
```

### 2. Define State Interface for Type Safety

```typescript
// ✅ Good - type-safe state management
interface CounterState {
count: number;
isIncreasing: boolean;
}

const { state, setState } = useComponentState<CounterState>({
count: 0,
isIncreasing: true
});
```

### 3. Use Functional State Updates

```typescript
// ✅ Good - safe with concurrent updates
setState(prevState => ({ ...prevState, value: newValue }));

// ❌ Bad - can overwrite concurrent updates
setState({ ...state, value: newValue });
```

### 4. Keep State Simple and Predictable

```typescript
// ✅ Good - simple state structure
const { state, setState } = useComponentState({
value: '',
isValid: false,
errors: []
});

// ❌ Avoid - overly complex nested state
const { state, setState } = useComponentState({
form: {
fields: {
user: {
profile: {
details: { ... }
}
}
}
}
});
```

**Note:** Cleanup is now automatic! The StateManager handles all cleanup when views unmount.

## IE 11 Compatibility

The StateManager is designed to work on Internet Explorer 11:

- **No ES6+ Features**: No Proxy, Map, Set, async/await, etc.
- **Polyfill-free**: Works with native ES5 features
- **Traditional Loops**: Uses for loops instead of forEach/map where performance matters
- **Manual Array Management**: Uses splice() instead of modern array methods

## Performance Considerations

- **Shallow Equality**: State changes use shallow comparison to prevent unnecessary re-renders
- **Batch Updates**: Multiple state updates are batched using `requestAnimationFrame` (with IE 11 fallback)
- **Stable Component IDs**: Uses first-render call order (like React hooks) for maximum stability
- **Memory Management**: Automatic cleanup prevents memory leaks
- **Single Frame Updates**: All setState calls within a frame are batched into a single re-render
- **Subscription Model**: Components can subscribe to state changes for efficient updates

### Batching System

The StateManager implements a batching system similar to React's:

```typescript
// These three setState calls happen in the same frame
setState({ count: 1 });
setState({ count: 2 });
setState({ count: 3 });

// Only triggers ONE re-render with final state: { count: 3 }
```

### Stable Component IDs

Component IDs are generated based on the **call order during the first render**, then locked in for subsequent renders:

```typescript
// First render: IDs assigned based on call order
{showHeader && Header()} // Gets ID: view-comp-0 (if rendered)
{Counter({ label: "Main" })} // Gets ID: view-comp-1 (always)
{showFooter && Footer()} // Gets ID: view-comp-2 (if rendered)

// Subsequent renders: Same IDs reused regardless of conditional rendering
// Components maintain their state even when order changes
```

**Key Benefits:**
- **Render-stable**: IDs don't change between re-renders
- **Call-order based**: Uses the same approach as React hooks
- **Conditional-safe**: Components keep state when conditionally rendered
- **No call stack dependency**: Avoids issues with JavaScript engine optimizations

## Troubleshooting

### Component Not Re-rendering

**Problem:** State changes but component doesn't re-render.

**Solution:** The StateManager automatically handles view integration. If components aren't re-rendering, check that:
1. Your component is being used within a view that extends `APMViewImpl`
2. The view's `render()` method is being called properly
3. State updates are using functional updates: `setState(prevState => ({ ...prevState, newValue }))`

### State Shared Between Components

**Problem:** Multiple component instances share the same state.

**Solution:** The StateManager automatically generates unique IDs for each component instance. This should not happen with the current system. If you encounter this issue:
1. Make sure you're using `useComponentState()` and not manually managing state
2. Check for any global variables that might be interfering
3. Verify components are rendered within proper view contexts

### Memory Leaks

**Problem:** State persists after component removal.

**Solution:** StateManager automatically cleans up when views unmount. For manual cleanup:

```typescript
cleanupComponentState(componentId);
```

### IE 11 Compatibility Issues

**Problem:** StateManager doesn't work on IE 11.

**Solution:** The StateManager is designed for IE 11 compatibility. If you encounter issues, check for:
- Modern JavaScript features in your component code
- Missing polyfills for other parts of your application
- Console errors that might indicate the root cause
Loading