Pattern 11: Cascading Updates
Part II: Interaction Patterns - Relationship Patterns
Opening Scenario: The Customer That Changed Everything
Tom was building an order management system for a regional distributor. The sales team would create orders by filling out a form with customer information, products, quantities, and shipping details.
One morning, Lisa from sales called Tom, frustrated.
"Tom, I just changed the customer from Johnson Hardware to Martinez Construction, and now I have to manually update everything. The shipping address is still Johnson's warehouse. The payment terms are still Net 30 instead of Martinez's Net 15. The sales tax is still calculating for Pennsylvania instead of Ohio. Why doesn't the system update all this automatically?"
Tom looked at his code:
// Bad: Fields are independent
function handleCustomerChange(newCustomer) {
// Just update the customer field
form.customer = newCustomer;
// User has to manually update everything else!
}
"I didn't think about that," Tom admitted. "Let me fix it."
He added some logic:
// Better: Update some related fields
function handleCustomerChange(newCustomer) {
form.customer = newCustomer;
// Update shipping address
form.shippingAddress = newCustomer.defaultShippingAddress;
// Update payment terms
form.paymentTerms = newCustomer.defaultPaymentTerms;
}
Lisa tested it. "Better! But the tax rate didn't update. And the salesperson field still shows my name - Martinez is actually Jennifer's account, not mine."
Tom realized the problem was deeper than he thought. When you change a customer, a cascade of updates should ripple through the form:
Customer changes →
├─ Shipping address updates (customer's default)
├─ Billing address updates (customer's billing address)
├─ Payment terms update (customer's terms)
├─ Sales tax rate updates (based on shipping state)
├─ Salesperson updates (account owner)
├─ Price list updates (customer's pricing tier)
├─ Available products update (customer's restrictions)
├─ Discount percentage updates (customer's negotiated discount)
├─ Currency updates (customer's preferred currency)
└─ All amounts recalculate (in new currency with new prices)
But it wasn't just about updating fields - it was about doing it intelligently:
Question 1: What if the user already modified the shipping address? - Don't overwrite their work - But maybe suggest the customer's default address - Track which fields the user has explicitly changed
Question 2: What if the new customer requires different products? - Clear incompatible line items - Show warning: "Johnson sells fasteners; Martinez buys lumber" - Offer to save current items and start fresh
Question 3: What order should updates happen in? - Shipping address must update before tax rate - Price list must update before line item prices - Currency must update before any amount calculations - Dependencies matter!
Question 4: Should updates be silent or visible? - User needs to see what changed - But not be overwhelmed with notifications - Highlight changed fields briefly
Six months later, Tom demonstrated the new system. Lisa selected a different customer:
[Customer changed from Johnson Hardware to Martinez Construction]
┌─────────────────────────────────────────────────────┐
│ The following fields were updated: │
│ │
│ ✓ Shipping Address → 789 Industrial Blvd, Toledo, OH│
│ ✓ Payment Terms → Net 15 │
│ ✓ Sales Tax → 5.75% (Ohio) │
│ ✓ Salesperson → Jennifer Martinez │
│ ✓ Price List → Construction (Tier 2) │
│ │
│ ⚠ Note: 3 line items are not available for this │
│ customer. They've been marked for review. │
│ │
│ [Keep Changes] [Undo All] [Review Item by Item] │
└─────────────────────────────────────────────────────┘
The system cascaded updates intelligently: - Updated all dependent fields in correct order - Preserved user edits where appropriate - Flagged incompatible data - Showed what changed and why - Gave user control to accept or reject
Order creation time dropped 40%. Data consistency improved dramatically. And Lisa stopped calling Tom with complaints.
Context
Cascading Updates applies when:
Fields are logically related: Changing one field affects others (customer → address → tax rate)
Consistency matters: Related fields must stay synchronized
User workflow is sequential: Users fill form top to bottom
Domain relationships exist: Business rules define dependencies
Manual updates are error-prone: Easy to forget to update all related fields
Context changes meaning: Changing one field changes what's valid in others
User time is valuable: Automation saves manual work
Problem Statement
Most forms treat fields as independent, forcing users to manually update related fields:
No automatic updates:
// Bad: User changes state, tax rate doesn't update
function handleStateChange(newState) {
form.state = newState;
// Tax rate still shows old state's rate
// User must manually update tax rate
}
Blind overwrites:
// Bad: Overwrites user's explicit choices
function handleCustomerChange(customer) {
form.shippingAddress = customer.defaultAddress;
// But what if user already entered a different address?
// Their work is lost!
}
Wrong update order:
// Bad: Updates in wrong sequence
function handleCustomerChange(customer) {
calculateTax(); // ← Uses old shipping address
updateShippingAddress(customer); // ← Now address is updated
// Tax was calculated with wrong address!
}
Silent failures:
// Bad: No feedback about what changed
function handleProductChange(product) {
form.price = product.price;
form.taxable = product.taxable;
form.shippingWeight = product.weight;
// User has no idea these fields changed
}
No conflict handling:
// Bad: Incompatible changes allowed
function handleCurrencyChange(newCurrency) {
form.currency = newCurrency;
// But amounts are still in old currency
// Prices may not be available in new currency
// No validation of compatibility
}
We need intelligent cascading that updates related fields in the right order, preserves user intent, and communicates changes clearly.
Forces
Automation vs Control
- Users want automatic updates
- But also want control over their data
- Balance convenience with autonomy
Completeness vs Performance
- Update all dependent fields thoroughly
- But updates can be slow
- Balance completeness with responsiveness
Transparency vs Clutter
- Users need to know what changed
- But too many notifications overwhelm
- Balance awareness with simplicity
Preservation vs Freshness
- Preserve user's explicit edits
- But update stale system-generated values
- Track which is which
Dependency Complexity
- Deep dependency chains are powerful
- But can be hard to understand/maintain
- Balance sophistication with maintainability
Solution
Implement intelligent cascade engine that tracks field relationships, determines update order through dependency graphs, preserves user edits, and communicates changes transparently with user control.
The pattern has five key elements:
1. Dependency Graph
Define relationships between fields:
class CascadeDependencyGraph {
constructor() {
this.dependencies = new Map();
this.fieldMetadata = new Map();
}
define(field, config) {
this.dependencies.set(field, {
triggers: config.triggers || [], // Fields that trigger updates to this field
updates: config.updates || [], // Fields this field triggers updates for
updateFn: config.updateFn, // Function to calculate new value
priority: config.priority || 0, // Update order priority
condition: config.condition // Optional: only update if condition met
});
this.fieldMetadata.set(field, {
userModified: false,
lastUpdatedBy: null, // 'user' or 'cascade'
lastUpdatedAt: null
});
}
// Example: Define customer-related cascades
defineCustomerCascades() {
this.define('customer', {
updates: [
'shippingAddress',
'billingAddress',
'paymentTerms',
'salesperson',
'priceList',
'currency'
],
priority: 1 // Highest priority - update first
});
this.define('shippingAddress', {
triggers: ['customer'],
updates: ['taxRate', 'shippingMethod'],
updateFn: (customer) => customer.defaultShippingAddress,
condition: (metadata) => !metadata.userModified, // Don't overwrite user edits
priority: 2
});
this.define('taxRate', {
triggers: ['shippingAddress', 'customer'],
updateFn: (form) => this.calculateTaxRate(form.shippingAddress, form.customer),
priority: 3 // Calculate after address updates
});
this.define('currency', {
triggers: ['customer'],
updates: ['allAmountFields'], // Cascade to all currency fields
updateFn: (customer) => customer.preferredCurrency,
priority: 2
});
this.define('priceList', {
triggers: ['customer'],
updates: ['lineItems'], // Line item prices need to recalculate
updateFn: (customer) => customer.pricingTier,
priority: 2
});
}
getUpdateOrder(changedField) {
// Build list of fields that need updating
const toUpdate = new Set();
const visited = new Set();
const collectDependents = (field) => {
if (visited.has(field)) return;
visited.add(field);
const config = this.dependencies.get(field);
if (config && config.updates) {
config.updates.forEach(dependent => {
toUpdate.add(dependent);
collectDependents(dependent); // Recursively get dependents
});
}
};
collectDependents(changedField);
// Sort by priority (higher priority first)
return Array.from(toUpdate).sort((a, b) => {
const priorityA = this.dependencies.get(a)?.priority || 0;
const priorityB = this.dependencies.get(b)?.priority || 0;
return priorityB - priorityA;
});
}
}
2. Cascade Engine
Execute updates intelligently:
class CascadeEngine {
constructor(dependencyGraph) {
this.graph = dependencyGraph;
this.pendingUpdates = [];
this.updateLog = [];
}
async cascade(changedField, newValue, form, context) {
// Mark field as user-modified
this.graph.fieldMetadata.get(changedField).userModified = true;
this.graph.fieldMetadata.get(changedField).lastUpdatedBy = 'user';
this.graph.fieldMetadata.get(changedField).lastUpdatedAt = new Date();
// Get fields that need updating
const fieldsToUpdate = this.graph.getUpdateOrder(changedField);
// Execute updates in order
const updates = [];
for (const field of fieldsToUpdate) {
const update = await this.updateField(field, form, context);
if (update) {
updates.push(update);
}
}
// Return all updates made
return {
changedField,
newValue,
cascadedUpdates: updates,
timestamp: new Date()
};
}
async updateField(field, form, context) {
const config = this.graph.dependencies.get(field);
const metadata = this.graph.fieldMetadata.get(field);
// Check condition - should we update this field?
if (config.condition && !config.condition(metadata, form, context)) {
return null; // Skip update
}
// Calculate new value
const oldValue = form[field];
const newValue = await config.updateFn(form, context);
// No change needed
if (oldValue === newValue) {
return null;
}
// Check for conflicts
if (metadata.userModified && !context.forceOverwrite) {
// User has explicitly set this - suggest instead of overwrite
return {
field,
oldValue,
newValue,
action: 'suggest',
reason: 'User has modified this field'
};
}
// Update the field
form[field] = newValue;
// Update metadata
metadata.lastUpdatedBy = 'cascade';
metadata.lastUpdatedAt = new Date();
// Log the update
return {
field,
oldValue,
newValue,
action: 'updated',
reason: `Based on ${config.triggers.join(', ')}`
};
}
getUpdateSummary(cascadeResult) {
const summary = {
totalUpdates: cascadeResult.cascadedUpdates.length,
updated: cascadeResult.cascadedUpdates.filter(u => u.action === 'updated'),
suggested: cascadeResult.cascadedUpdates.filter(u => u.action === 'suggest'),
skipped: cascadeResult.cascadedUpdates.filter(u => u.action === 'skip')
};
return summary;
}
}
3. User Edit Tracking
Distinguish user edits from cascade updates:
class EditTracker {
constructor() {
this.editHistory = new Map();
}
recordUserEdit(field, value, timestamp = new Date()) {
if (!this.editHistory.has(field)) {
this.editHistory.set(field, []);
}
this.editHistory.get(field).push({
value,
source: 'user',
timestamp
});
}
recordCascadeUpdate(field, value, trigger, timestamp = new Date()) {
if (!this.editHistory.has(field)) {
this.editHistory.set(field, []);
}
this.editHistory.get(field).push({
value,
source: 'cascade',
trigger,
timestamp
});
}
wasUserModified(field) {
const history = this.editHistory.get(field);
if (!history || history.length === 0) return false;
// Check if most recent edit was by user
const recent = history[history.length - 1];
return recent.source === 'user';
}
getLastUserValue(field) {
const history = this.editHistory.get(field);
if (!history) return null;
// Find most recent user edit
for (let i = history.length - 1; i >= 0; i--) {
if (history[i].source === 'user') {
return history[i].value;
}
}
return null;
}
shouldPreserveValue(field, newValue) {
if (!this.wasUserModified(field)) {
return false; // Not user modified, safe to update
}
const lastUserValue = this.getLastUserValue(field);
// If new value same as what user entered, no conflict
if (newValue === lastUserValue) {
return false;
}
// User modified it to something different - preserve it
return true;
}
}
4. Conflict Resolution
Handle cases where cascade conflicts with user edits:
class ConflictResolver {
constructor() {
this.strategies = {
'preserve_user': this.preserveUserEdit,
'suggest_update': this.suggestUpdate,
'force_update': this.forceUpdate,
'ask_user': this.askUser
};
}
async resolve(conflict, strategy = 'suggest_update') {
const resolver = this.strategies[strategy];
if (!resolver) {
throw new Error(`Unknown strategy: ${strategy}`);
}
return await resolver.call(this, conflict);
}
preserveUserEdit(conflict) {
// Keep user's value, don't update
return {
action: 'preserve',
field: conflict.field,
value: conflict.oldValue,
reason: 'Preserving user edit'
};
}
suggestUpdate(conflict) {
// Don't update, but show suggestion
return {
action: 'suggest',
field: conflict.field,
currentValue: conflict.oldValue,
suggestedValue: conflict.newValue,
reason: conflict.reason,
canAccept: true,
canDismiss: true
};
}
forceUpdate(conflict) {
// Override user's value
return {
action: 'update',
field: conflict.field,
value: conflict.newValue,
reason: 'Required for consistency'
};
}
async askUser(conflict) {
// Prompt user to choose
return {
action: 'prompt',
field: conflict.field,
options: [
{ label: `Keep my value: ${conflict.oldValue}`, value: conflict.oldValue },
{ label: `Use suggested: ${conflict.newValue}`, value: conflict.newValue }
],
reason: conflict.reason
};
}
getBatchStrategy(conflicts, context) {
// For multiple conflicts, determine best strategy
if (conflicts.length === 0) {
return 'preserve_user';
}
if (conflicts.length === 1) {
return 'suggest_update';
}
if (conflicts.length > 5) {
// Too many to show individually
return 'ask_user'; // Batch prompt
}
// Critical fields must update
const critical = conflicts.filter(c => c.critical);
if (critical.length > 0) {
return 'force_update';
}
return 'suggest_update';
}
}
5. User Feedback
Communicate changes clearly:
class CascadeFeedback {
showUpdateSummary(cascadeResult, options = {}) {
const updates = cascadeResult.cascadedUpdates;
if (updates.length === 0) {
return null; // Nothing to show
}
// Group updates by action
const updated = updates.filter(u => u.action === 'updated');
const suggested = updates.filter(u => u.action === 'suggest');
// Build notification
const notification = {
title: this.getTitle(cascadeResult.changedField),
updates: [],
suggestions: [],
actions: []
};
// Show updates
updated.forEach(update => {
notification.updates.push({
field: this.getFieldLabel(update.field),
oldValue: update.oldValue,
newValue: update.newValue,
icon: '✓'
});
});
// Show suggestions
suggested.forEach(suggestion => {
notification.suggestions.push({
field: this.getFieldLabel(suggestion.field),
currentValue: suggestion.oldValue,
suggestedValue: suggestion.newValue,
reason: suggestion.reason,
actions: [
{ label: 'Accept', action: () => this.acceptSuggestion(suggestion) },
{ label: 'Dismiss', action: () => this.dismissSuggestion(suggestion) }
]
});
});
// Add batch actions
if (updated.length > 0) {
notification.actions.push({
label: 'Undo All',
action: () => this.undoAll(cascadeResult)
});
}
if (suggested.length > 0) {
notification.actions.push({
label: 'Accept All Suggestions',
action: () => this.acceptAllSuggestions(suggested)
});
}
return notification;
}
getTitle(changedField) {
const labels = {
'customer': 'Customer Changed',
'shippingAddress': 'Shipping Address Changed',
'currency': 'Currency Changed'
};
return labels[changedField] || 'Field Updated';
}
visuallyHighlightChanges(updates, duration = 3000) {
updates.forEach(update => {
const element = document.getElementById(`field-${update.field}`);
if (element) {
// Add highlight class
element.classList.add('cascade-updated');
// Remove after duration
setTimeout(() => {
element.classList.remove('cascade-updated');
}, duration);
}
});
}
showInlineNotification(field, message, type = 'info') {
const fieldElement = document.getElementById(`field-${field}`);
if (!fieldElement) return;
const notification = document.createElement('div');
notification.className = `inline-notification ${type}`;
notification.innerHTML = `
<span class="icon">${this.getIcon(type)}</span>
<span class="message">${message}</span>
`;
fieldElement.appendChild(notification);
// Auto-dismiss after 5 seconds
setTimeout(() => {
notification.remove();
}, 5000);
}
getIcon(type) {
const icons = {
'info': 'ℹ️',
'success': '✓',
'warning': '⚠️',
'error': '✗'
};
return icons[type] || icons['info'];
}
}
Implementation Details
Complete Cascading System
class IntelligentCascadeSystem {
constructor() {
this.graph = new CascadeDependencyGraph();
this.engine = new CascadeEngine(this.graph);
this.tracker = new EditTracker();
this.resolver = new ConflictResolver();
this.feedback = new CascadeFeedback();
// Define all cascade relationships
this.defineRelationships();
}
defineRelationships() {
// Customer cascades
this.graph.define('customer', {
updates: ['shippingAddress', 'billingAddress', 'paymentTerms',
'salesperson', 'priceList', 'currency'],
updateFn: async (form, context) => {
const customer = await this.getCustomer(form.customer);
return customer;
},
priority: 1
});
this.graph.define('shippingAddress', {
triggers: ['customer'],
updates: ['taxRate', 'shippingMethod'],
updateFn: (form, context) => {
const customer = context.customerData;
return customer.defaultShippingAddress;
},
condition: (metadata) => !metadata.userModified,
priority: 2
});
this.graph.define('taxRate', {
triggers: ['shippingAddress'],
updateFn: async (form, context) => {
return await this.calculateTaxRate(form.shippingAddress);
},
priority: 3
});
// Product cascades
this.graph.define('product', {
updates: ['price', 'taxable', 'shippingWeight', 'category'],
updateFn: async (form, context) => {
const product = await this.getProduct(form.product);
return product;
},
priority: 1
});
this.graph.define('price', {
triggers: ['product', 'priceList', 'currency'],
updateFn: async (form, context) => {
const product = await this.getProduct(form.product);
const priceList = form.priceList;
const currency = form.currency;
return this.getPrice(product, priceList, currency);
},
priority: 2
});
// Currency cascades
this.graph.define('currency', {
triggers: ['customer'],
updates: ['price', 'subtotal', 'tax', 'total'],
updateFn: (form, context) => {
const customer = context.customerData;
return customer.preferredCurrency;
},
priority: 1
});
}
async handleFieldChange(field, newValue, form, context = {}) {
// Record user edit
this.tracker.recordUserEdit(field, newValue);
// Update form
form[field] = newValue;
// Fetch any necessary data
const enrichedContext = await this.enrichContext(field, newValue, context);
// Execute cascade
const result = await this.engine.cascade(field, newValue, form, enrichedContext);
// Handle conflicts
const conflicts = result.cascadedUpdates.filter(u => u.action === 'suggest');
if (conflicts.length > 0) {
const resolution = await this.resolver.resolve(conflicts[0], 'suggest_update');
// Handle resolution...
}
// Show feedback
const notification = this.feedback.showUpdateSummary(result);
if (notification) {
this.displayNotification(notification);
}
// Highlight changed fields
this.feedback.visuallyHighlightChanges(result.cascadedUpdates);
return result;
}
async enrichContext(field, value, baseContext) {
const context = { ...baseContext };
// Fetch customer data if customer changed
if (field === 'customer') {
context.customerData = await this.getCustomer(value);
}
// Fetch product data if product changed
if (field === 'product') {
context.productData = await this.getProduct(value);
}
return context;
}
async getCustomer(customerId) {
// Fetch from database/API
return {
id: customerId,
name: 'Martinez Construction',
defaultShippingAddress: {
street: '789 Industrial Blvd',
city: 'Toledo',
state: 'OH',
zip: '43612'
},
defaultBillingAddress: {
street: '789 Industrial Blvd',
city: 'Toledo',
state: 'OH',
zip: '43612'
},
defaultPaymentTerms: 'Net 15',
pricingTier: 'Construction - Tier 2',
preferredCurrency: 'USD',
accountOwner: 'Jennifer Martinez'
};
}
async calculateTaxRate(address) {
const taxRates = {
'PA': 0.06,
'OH': 0.0575,
'NY': 0.08875
};
return taxRates[address.state] || 0;
}
}
// Usage
const cascade = new IntelligentCascadeSystem();
// User changes customer
const form = {
customer: 'Johnson Hardware',
shippingAddress: { city: 'Pittsburgh', state: 'PA' },
taxRate: 0.06
};
await cascade.handleFieldChange('customer', 'Martinez Construction', form);
// Result:
// form.customer = 'Martinez Construction'
// form.shippingAddress = { city: 'Toledo', state: 'OH' } (cascaded)
// form.taxRate = 0.0575 (cascaded from address)
// form.paymentTerms = 'Net 15' (cascaded)
// ... etc.
Consequences
Benefits
Reduced Manual Work: - Users don't update related fields manually - Saves time and effort - Reduces cognitive load
Improved Consistency: - Related fields stay synchronized - No orphaned or stale data - Business rules enforced automatically
Fewer Errors: - Can't forget to update dependent fields - Correct values calculated automatically - Relationships maintained
Better UX: - Form feels intelligent - Reduces frustration - Faster task completion
Preserved Intent: - User edits protected - System updates when appropriate - Smart conflict resolution
Liabilities
Complexity: - Dependency graphs can be intricate - Circular dependencies must be avoided - Testing all combinations is challenging
Performance: - Deep cascades can be slow - May need async updates - Progress indicators helpful
User Confusion: - "Why did this field change?" - Need clear communication - Transparency essential
Unintended Consequences: - Cascade may update unexpected fields - User may not want updates - Need undo/control mechanisms
Maintenance: - Relationship rules change - Dependencies evolve - Documentation critical
Domain Examples
E-commerce: Product Selection Cascade
// Product selection cascades to category, price, inventory
defineProductCascades() {
this.cascade('product', {
updates: [
{
field: 'category',
fn: (product) => product.category
},
{
field: 'price',
fn: (product, form) => this.getPrice(product, form.priceList),
condition: (meta) => !meta.userModified // Preserve negotiated prices
},
{
field: 'inventory',
fn: async (product) => await this.checkInventory(product.sku),
display: 'inline' // Show inline, not in notification
},
{
field: 'shippingWeight',
fn: (product) => product.weight
}
]
});
}
Healthcare: Insurance Selection Cascade
// Insurance selection cascades to coverage, copay, authorization rules
defineInsuranceCascades() {
this.cascade('insurancePlan', {
updates: [
{
field: 'copayAmount',
fn: (plan, form) => plan.getCopay(form.visitType)
},
{
field: 'requiresAuthorization',
fn: (plan, form) => plan.requiresAuth(form.procedure)
},
{
field: 'coveragePercentage',
fn: (plan) => plan.coveragePercent
},
{
field: 'deductibleRemaining',
fn: async (plan, form) =>
await this.getDeductibleBalance(form.patient, plan)
}
],
onComplete: (updates) => {
// Calculate patient responsibility
this.calculatePatientResponsibility(updates);
}
});
}
Real Estate: Property Type Cascade
// Property type cascades to available features, required fields
definePropertyTypeCascades() {
this.cascade('propertyType', {
updates: [
{
field: 'availableFeatures',
fn: (type) => this.getFeaturesForType(type),
action: 'replace_list'
},
{
field: 'requiredDisclosures',
fn: (type, form) => this.getRequiredDisclosures(type, form.state),
action: 'show_checklist'
},
{
field: 'squareFootage',
fn: (type) => null, // Clear value
condition: (meta, form, type) =>
type === 'land' && form.squareFootage // Land doesn't have sq ft
}
],
validation: (form) => {
// Ensure property type compatible with listing type
if (form.listingType === 'residential' &&
!['house', 'condo', 'townhouse'].includes(form.propertyType)) {
return {
valid: false,
message: 'Property type must be residential'
};
}
return { valid: true };
}
});
}
Related Patterns
Prerequisites: - Volume 3, Pattern 7: Calculated Dependencies (mathematical cascades) - Volume 3, Pattern 9: Contextual Constraints (what's valid changes)
Synergies: - Volume 3, Pattern 8: Intelligent Defaults (cascades are smart defaults) - Volume 3, Pattern 10: Semantic Suggestions (suggest during cascade) - Volume 3, Pattern 14: Cross-Field Validation (validate after cascade)
Conflicts: - User autonomy (cascades can feel too automatic) - Audit requirements (must track all changes)
Alternatives: - Manual updates (let user update everything) - Wizard flow (step-by-step with explicit propagation) - Refresh button (user triggers cascade explicitly)
Known Uses
Salesforce: Changing account cascades to contacts, opportunities, and cases
QuickBooks: Changing customer updates default terms, tax settings, and pricing
E-commerce Platforms (Shopify, WooCommerce): Product selection cascades to price, inventory, shipping
Travel Booking (Expedia, Kayak): Changing destination cascades to hotel options, car rentals, activities
CRM Systems: Contact changes cascade to related opportunities, cases, and activities
ERP Systems (SAP, Oracle): Part number selection cascades to specifications, pricing, and inventory
Further Reading
Academic Foundations
- Reactive Programming: Bainomugisha, E., et al. (2013). "A Survey on Reactive Programming." ACM Computing Surveys 45(4). https://dl.acm.org/doi/10.1145/2501654.2501666
- Data Flow Programming: Johnston, W.M., Hanna, J.R.P., & Millar, R.J. (2004). "Advances in Dataflow Programming Languages." ACM Computing Surveys 36(1): 1-34.
- Dependency Management: Mens, T., et al. (2005). "Challenges in Software Evolution." ICSM '05.
Practical Implementation
- MobX: https://mobx.js.org/ - Simple, scalable reactive state management
- Vue.js Reactivity: https://vuejs.org/guide/essentials/reactivity-fundamentals.html - Reactive data binding
- RxJS: https://rxjs.dev/ - Reactive Extensions for JavaScript
- Immer: https://immerjs.github.io/immer/ - Immutable state with draft changes
- Recoil: https://recoiljs.org/ - State management with derived atoms
Standards & Patterns
- Observer Pattern: https://refactoring.guru/design-patterns/observer - Classic dependency notification
- Publish-Subscribe: https://docs.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber - Event-driven architecture
Related Trilogy Patterns
- Pattern 12: Calculated Dependencies - Cascades drive calculations
- Pattern 14: Contextual Constraints - Cascades update constraints
- Pattern 5: Form State Tracking - Track cascade state changes
- Volume 2, Chapter 1: The Universal Event Log - Audit cascade history
- Volume 1, Chapter 9: User Experience Design - Cascade as transformation
Tools & Libraries
- json-schema: https://json-schema.org/ - Schema dependencies and conditionals
- Formik Field Dependencies: https://formik.org/docs/examples/dependent-fields
- React Hook Form Watch: https://react-hook-form.com/api/useform/watch - Track field changes
- Zod Transforms: https://zod.dev/?id=transform - Schema transformations
Implementation Examples
- Dependent Dropdowns: https://css-tricks.com/dynamic-dropdowns/ - Practical tutorial
- Cascading Selects Pattern: https://uxplanet.org/designing-cascading-dropdowns-for-better-ux-e9d4f1fcf5f0