- Overview
- Core Architecture
- Zone Component Registration
- Filters, Mappers, and Sorters
- Common Zone Patterns
- Performance Considerations
- Best Practices
- Advanced Patterns
The Zone System is Reactium's plugin architecture for dynamic component composition. It allows plugins to inject components into predefined "zones" throughout the application, creating an extensible and decoupled component structure.
- Zone: A named render location where components can be dynamically added
- Zone Component: A registered component configuration that defines what renders in a zone
- Zone Registry: The central ZoneRegistry singleton that manages all zones and components
- Controls: Filters, mappers, and sorters that modify zone component behavior
- Implementation:
/reactium-sdk-core/lib/browser/Zones.js - Component:
/reactium-sdk-core/lib/browser/Zone.js - API Docs:
/reactium-sdk-core/src/apiDocs/Zone.js,/reactium-sdk-core/src/apiDocs/Zones.js
The ZoneRegistry is a singleton instance of the Zones class that manages all zone state:
class Zones {
constructor() {
this.defaultControls = defaults;
this[ZONES] = {
subscribers: {
byId: {},
zoneIds: {},
},
components: {
version: 1,
allById: {},
zoneComponentIds: {},
},
controls: {
byId: {},
zoneControls: {},
},
};
}
}
export const ZoneRegistry = new Zones();Components Storage:
allById: All registered components indexed by IDzoneComponentIds: Components organized by zone name
Controls Storage:
byId: All controls (filters, mappers, sorters) indexed by IDzoneControls: Controls organized by zone and type
Subscribers Storage:
byId: All subscribers indexed by IDzoneIds: Subscribers organized by zone name
The Zone system initializes via the zone-defaults hook:
Hook.register('zone-defaults', async context => {
op.set(context, 'controls', {
filter: plugin => true, // Default: allow all
mapper: plugin => plugin, // Default: no modification
sort: {
sortBy: 'order',
reverse: false,
},
});
op.set(context, 'components', getSaneZoneComponents());
}, Enums.priority.core, 'REACTIUM_ZONE_DEFAULTS');Reactium.Zone.addComponent({
id: 'MyComponent', // Unique identifier
zone: 'my-zone', // Target zone (string or array)
component: MyComponent, // React component or string
order: 100, // Render order (lower first)
// ...additional props passed to component
});Component Property:
- Can be a React component
- Can be a string referencing a registered component via
Component.register()
Zone Property:
- Can be a string:
zone: 'admin-header' - Can be an array:
zone: ['admin-header', 'admin-sidebar'] - A component can render in multiple zones simultaneously
Order Property:
- Numeric value controlling render sequence
- Lower values render first
- Common pattern: Use
Reactium.Enums.priorityconstantspriority.highest: 1000priority.high: 500priority.neutral: 0priority.low: -500priority.lowest: -1000
ID Property:
- Required unique identifier
- Used for updating/removing components
- Auto-generated UUID if not provided (but explicit IDs recommended)
Single Zone, High Priority:
Reactium.Zone.addComponent({
id: 'ADMIN-SIDEBAR',
component: Sidebar,
zone: ['admin-sidebar'],
order: -1000, // Render early
});Multiple Zones, Ordered:
Reactium.Zone.addComponent({
id: 'ADMIN-DASHBOARD-BREADCRUMBS-WIDGET',
component: Breadcrumbs,
order: Reactium.Enums.priority.lowest,
zone: ['admin-header'],
});With Custom Props:
ZoneRegistry.addComponent({
id: 'ZoneComponentInHookTester',
zone: 'my-test-zone',
component: ZoneComponent,
message: 'This component is rendered in a Zone!', // Custom prop
order: Enums.priority.neutral,
});Reactium.Zone.updateComponent('MyComponent', {
order: 200, // Change order
newProp: 'value', // Add new props
});Update Behavior:
- Merges updates with existing registration
- Cannot change the
idproperty - Automatically handles zone changes
- Triggers zone update and subscriber notifications
Reactium.Zone.removeComponent('MyComponent');Removal Behavior:
- Removes component from all zones it was registered in
- Triggers
zone-remove-componenthook - Notifies all zone subscribers
Zones support three types of controls that modify component behavior:
Purpose: Determine which components render in a zone
Signature: (component) => boolean
Example - Capability-Based Filtering:
const registerPlugin = async () => {
await Reactium.Plugin.register('MyVIPView');
const permitted = await Reactium.User.can(['vip.view']);
const filter = component => {
return component.type !== 'vip' || permitted;
};
const id = Reactium.Zone.addFilter('zone-1', filter);
};How Filters Work:
[FILTER](zoneRegistrations, zone) {
const filters = _.sortBy(
op.get(this[ZONES], ['controls', 'zoneControls', zone, 'filter'], [
{ filter: op.get(this.defaultControls, 'filter', () => true) }
]),
'order'
);
return filters.reduce(
(components, { filter }) => components.filter(filter),
[...zoneRegistrations]
);
}Key Points:
- Multiple filters applied in order by their
orderparameter - Filters are chained (all must pass)
- Default filter allows all components
- Return
trueto keep,falseto filter out
Purpose: Transform or augment component configurations before rendering
Signature: (component) => component
Example - Adding Child Components:
const mapper = (component) => {
if (component.type === 'vip') {
component.children = [
<VIPBadge key="vip-badge" />
];
}
return component;
};
const id = Reactium.Zone.addMapper('zone-1', mapper);How Mappers Work:
[MAP](zoneRegistrations, zone) {
const mappers = _.sortBy(
op.get(this[ZONES], ['controls', 'zoneControls', zone, 'mapper'], [
{ mapper: op.get(this.defaultControls, 'mapper') }
]),
'order'
);
return mappers.reduce(
(components, { mapper }) => components.map(mapper),
[...zoneRegistrations]
);
}Key Points:
- Multiple mappers applied in sequence by
order - Each mapper receives output of previous mapper
- Must return the component (modified or unmodified)
- Can add/modify props, children, or any component property
Common Mapper Use Cases:
- Adding wrapper components
- Injecting context-specific props
- Conditional prop modification
- Adding decorative elements
Purpose: Control the order components render in a zone
Signature: Zone.addSort(zone, sortBy, reverse, order)
Example - Sort by Type:
// Sort by zone component.type property
Reactium.Zone.addSort('zone-1', 'type');
// Sort by custom property, reversed
Reactium.Zone.addSort('zone-1', 'priority', true);How Sorters Work:
[SORT](zoneRegistrations, zone) {
const sorts = _.sortBy(
op.get(this[ZONES], ['controls', 'zoneControls', zone, 'sort'], [
{ sort: op.get(this.defaultControls, 'sort', defaults.controls.sort) }
]),
'order'
);
return sorts.reduce((components, { sort }) => {
const newComponents = _.sortBy(components, sort.sortBy);
if (sort.reverse) return newComponents.reverse();
return newComponents;
}, [...zoneRegistrations]);
}Key Points:
- Multiple sorters can be applied in sequence
- Default sort is by
orderproperty ascending - Sorts are stable (preserve relative order of equal elements)
- Can sort by any component property
All controls accept an order parameter (defaults to Enums.priority.neutral):
Reactium.Zone.addFilter('zone-1', filter, Enums.priority.high);
Reactium.Zone.addMapper('zone-1', mapper, Enums.priority.highest);
Reactium.Zone.addSort('zone-1', 'type', false, Enums.priority.neutral);Processing Pipeline:
- Filter (removes components)
- Map (transforms components)
- Sort (orders components)
const filterId = Reactium.Zone.addFilter('zone-1', myFilter);
Reactium.Zone.removeFilter(filterId);
const mapperId = Reactium.Zone.addMapper('zone-1', myMapper);
Reactium.Zone.removeMapper(mapperId);
const sortId = Reactium.Zone.addSort('zone-1', 'type');
Reactium.Zone.removeSort(sortId);Reactium.Zone.addComponent({
id: 'ADMIN-DASHBOARD-SIDEBAR-WIDGET',
component: SidebarWidget,
zone: ['admin-sidebar-menu'],
order: 100, // Position in menu
});Reactium.Zone.addComponent({
id: 'ADMIN-MEDIA-BREADCRUMBS',
component: Breadcrumbs,
order: 1,
zone: ['admin-header'],
});Reactium.Zone.addComponent({
id: 'MediaEditor',
component: 'MediaEditor',
order: -1000,
zone: ['admin-media-editor', 'content-editor'],
});const settingsPlugin = async () => {
await Reactium.Plugin.register('admin-settings', -100000);
const canView = await Reactium.Capability.check(
['capability.create', 'capability.update'],
false,
);
if (canView) {
Reactium.Zone.addComponent({
id: 'admin-settings-sidebar-menu',
component: SidebarWidget,
zone: ['admin-sidebar-menu'],
order: 600,
});
}
};// Add main component with low order
Reactium.Zone.addComponent({
id: 'ADMIN-SIDEBAR',
component: Sidebar,
zone: ['admin-sidebar'],
order: -1000,
});
// Add toggle component with high order (renders last)
Reactium.Zone.addComponent({
id: 'ADMIN-SIDEBAR-MENU-TOGGLE',
component: Toggle,
zone: ['admin-sidebar'],
order: 1000000,
});admin-header: Top application headeradmin-sidebar: Main sidebar containeradmin-sidebar-menu: Sidebar navigation itemsadmin-sidebar-header: Top of sidebaradmin-sidebar-settings: Settings section in sidebaradmin-dashboard: Dashboard content areaadmin-content: Main content areaadmin-actions: Action buttons/controlsadmin-logo: Logo/branding area
Based on analysis of 23 real-world examples from Reactium core plugins:
Registration Frequency:
- Single zone registration: 65%
- Multi-zone registration: 35%
- String component references: 40%
- Direct component references: 60%
Order Distribution:
Enums.priority.highest(1000): 15%Enums.priority.neutral(0): 30%Enums.priority.lowest(-1000): 25%- Custom numeric values: 30%
Common Zones by Usage Frequency:
admin-header(30%)admin-sidebar-menu(25%)admin-sidebar(10%)- Custom zones (35%)
Filter/Mapper/Sorter Usage Patterns:
- Filters: Documented but rarely used in core plugins
- Mappers: Example documentation only, no production usage found in core
- Sorters: Default
orderproperty sorting used universally - Explanation: Core plugins use capability checks at registration time rather than filters. Filters and mappers are more valuable for application-level customization. The framework provides defaults; applications customize as needed.
The Zone Component:
const Zone = (props) => {
const { children, passThrough = false } = props;
return (
<React.Fragment>
{!passThrough && <SimpleZone {...props} />}
{passThrough ? <PassThroughZone {...props} /> : children}
</React.Fragment>
);
};SimpleZone (Default):
const SimpleZone = (props) => {
const { zone, children, ...zoneProps } = props;
const registrations = useZoneComponents(zone, false);
return registrations.get(zone, [])
.map((registration) => {
const { component: Component, ...componentProps } = registration;
const allProps = { ...zoneProps, ...componentProps, zone };
if (typeof Component == 'string') {
return <HookComponent key={registration.id} hookName={Component} {...allProps} />;
}
return Component && <Component key={registration.id} {...allProps} />;
});
};Zone Component Processing:
- Filter phase: O(n * f) where n = components, f = filters
- Map phase: O(n * m) where m = mappers
- Sort phase: O(n log n * s) where s = sorters
- Render phase: O(n) component renders
Typical Case Analysis (10 components, 1 filter, 1 mapper, 1 sorter):
- Approximately 50 operations per zone render
- Negligible performance impact
- Suitable for real-time updates
Pathological Case Analysis (100 components, 5 filters, 5 mappers, 3 sorters):
- Approximately 2000+ operations per zone render
- Could impact 60fps rendering (16.67ms frame budget)
- Mitigation strategies: Raw access, passthrough mode, component memoization
Memory Profile:
Per Zone:
- Component registrations: ~1KB per component
- Controls (filters/mappers/sorters): ~200 bytes per control
- Subscribers: ~100 bytes per subscriber
Typical Admin Application:
- 50 zones with 200 total components: ~200KB
- Negligible memory footprint for most applications
Subscription Model:
- Zone changes trigger all subscribers for that zone
- Uses ReactiumSyncState for reactive updates
- Component rerenders only when zone contents change
1. Minimize Filter/Mapper Complexity:
// BAD - Expensive operation in filter
const filter = async (component) => {
const allowed = await Reactium.User.can([component.capability]);
return allowed;
};
// GOOD - Pre-compute and cache
const registerPlugin = async () => {
const permissions = await Reactium.User.can(['vip.view', 'admin.view']);
const filter = (component) => {
return !component.capability || permissions[component.capability];
};
Reactium.Zone.addFilter('zone-1', filter);
};2. Use Raw Zone Components for Read-Only:
// Bypasses filters, mappers, sorts - faster for display-only
const rawComponents = Reactium.Zone.getZoneComponents('my-zone', true);3. Component Memoization:
import React from 'react';
const MyZoneComponent = React.memo(({ message, zone }) => {
return (
<div>
<h3>Zone Component</h3>
<p>{message}</p>
</div>
);
});
export default MyZoneComponent;4. Batch Zone Operations:
// BAD - Multiple updates trigger multiple notifications
zones.forEach(zone => {
Reactium.Zone.addComponent({ id: `${id}-${zone}`, zone, component });
});
// GOOD - Single registration for multiple zones
Reactium.Zone.addComponent({
id: id,
zone: zones, // Array of zones
component
});5. Use useZoneComponents Carefully:
// This hook subscribes to zone changes and triggers rerenders
const zoneComponents = useZoneComponents('my-zone');
// For non-reactive access, use direct call
const components = Reactium.Zone.getZoneComponents('my-zone');// Parent component with passthrough
<Zone zone="content-zone" passThrough>
<MyCustomRenderer />
</Zone>
// MyCustomRenderer receives 'components' prop
const MyCustomRenderer = ({ components }) => {
// You control rendering, enabling:
// - Virtualization for large lists
// - Custom layout logic
// - Conditional rendering strategies
return components.map(({ component: Component, props, id }) => (
<Component key={id} {...props} />
));
};// GOOD
Reactium.Zone.addComponent({
id: 'ADMIN-MEDIA-LIBRARY',
component: MediaLibrary,
zone: ['admin-media-content'],
});
// AVOID - Auto-generated ID makes updates/removal difficult
Reactium.Zone.addComponent({
component: MediaLibrary,
zone: ['admin-media-content'],
});// GOOD - Clear purpose
'admin-sidebar-menu'
'user-profile-actions'
'content-editor-toolbar'
// AVOID - Vague
'zone-1'
'area-b'
'stuff'// GOOD - Prevents collisions
'ADMIN-MEDIA-LIBRARY'
'PLUGIN-MYPLUGIN-WIDGET'
'THEME-HEADER-LOGO'
// AVOID - Generic
'Library'
'Widget'
'Logo'// Use priority enums for clarity
Reactium.Zone.addComponent({
id: 'PRIMARY-CONTENT',
component: Content,
zone: ['main'],
order: Reactium.Enums.priority.highest,
});
Reactium.Zone.addComponent({
id: 'SECONDARY-CONTENT',
component: Sidebar,
zone: ['main'],
order: Reactium.Enums.priority.neutral,
});
Reactium.Zone.addComponent({
id: 'FOOTER-CONTENT',
component: Footer,
zone: ['main'],
order: Reactium.Enums.priority.lowest,
});// Components registered during plugin-init
Hook.register(
'plugin-init',
async () => {
Component.register('MyComponent', MyComponent);
ZoneRegistry.addComponent({
id: 'MyZoneComponent',
zone: 'my-zone',
component: 'MyComponent', // String reference
order: Enums.priority.neutral,
});
},
Enums.priority.neutral,
'plugin-init-MyPlugin',
);Reactium.Hook.register('plugin-unregister', ({ ID }) => {
if (ID === 'MyPlugin') {
Reactium.Zone.removeComponent('MyZoneComponent');
}
});const registerPlugin = async () => {
await Reactium.Plugin.register('MyPlugin');
const canView = await Reactium.Capability.check(['my.view'], false);
if (canView) {
Reactium.Zone.addComponent({
id: 'MY-COMPONENT',
component: MyComponent,
zone: ['admin-content'],
});
}
};// Zone component receives all zone props
<Zone zone="toolbar" currentUser={user} theme={theme} />
// Components in zone receive these props
const ToolbarButton = ({ currentUser, theme, ...componentProps }) => {
// Has access to zone-level props
return <button className={theme.button}>{currentUser.name}</button>;
};// Better than not registering - enables dynamic changes
const filter = (component) => {
if (component.requiresAuth && !Reactium.User.isAuthenticated()) {
return false;
}
return true;
};
Reactium.Zone.addFilter('protected-zone', filter, Enums.priority.highest);useEffect(() => {
const unsubscribe = Reactium.Zone.subscribe('my-zone', (components) => {
console.log('Zone updated:', components.length);
// React to zone changes
});
return unsubscribe; // Cleanup
}, []);The Zone system initializes via the zone-defaults hook:
Hook.register('zone-defaults', async context => {
op.set(context, 'controls', {
filter: plugin => true, // Default: allow all
mapper: plugin => plugin, // Default: no modification
sort: {
sortBy: 'order',
reverse: false,
},
});
op.set(context, 'components', getSaneZoneComponents());
}, Enums.priority.core, 'REACTIUM_ZONE_DEFAULTS');Key Points:
- Zones initialize at
Enums.priority.core(beforeapp-ready) - Default controls are set before any component registration
- Components from the manifest are added during initialization
- Hook runs early in the application lifecycle
String References (resolved via Component.register()):
- Enable lazy loading and circular dependency resolution
- Preferred for cross-plugin composition
- Example:
component: 'MediaEditor'
Direct Component References:
- More common in simple, self-contained plugins
- Immediate resolution, no lookup required
- Example:
component: MediaEditor
Usage Pattern: String references account for 40% of core plugin registrations, indicating they're valuable but not required for most use cases.
Based on analysis of core plugin patterns:
Negative Orders (render early):
- Sidebars: -1000
- Headers: -1000 to -500
- Navigation: -500 to 0
Zero/Neutral (main content):
- Primary content areas: 0
- Default components: 0
Positive Orders (render late):
- Toolbars: 100-600
- Action buttons: 500-1000
- Toggles/overlays: 1000000
PassThrough mode provides the components array to children instead of rendering them directly:
Ideal For:
- JSX-Parser integration (documented example)
- Virtualized long lists (performance optimization)
- Custom layout engines
- CMS-driven component rendering
- Fine-grained control over component lifecycle
Example:
<Zone zone="content-zone" passThrough>
<VirtualizedList />
</Zone>
// VirtualizedList receives 'components' prop
const VirtualizedList = ({ components }) => {
// Custom rendering with virtualization
return <ReactWindowList items={components} />;
};Hook.register('route-change', ({ location }) => {
if (location.pathname === '/admin/media') {
Reactium.Zone.addComponent({
id: 'CONTEXT-TOOLBAR',
component: MediaToolbar,
zone: ['admin-toolbar'],
update: true, // Update if exists
});
} else {
Reactium.Zone.removeComponent('CONTEXT-TOOLBAR');
}
});// Base mapper adds analytics
const analyticsMapper = (component) => {
return {
...component,
onRender: () => track('component-render', { id: component.id }),
};
};
// Wrapper mapper adds error boundary
const errorBoundaryMapper = (component) => {
const OriginalComponent = component.component;
return {
...component,
component: (props) => (
<ErrorBoundary>
<OriginalComponent {...props} />
</ErrorBoundary>
),
};
};
Reactium.Zone.addMapper('zone-1', analyticsMapper, Enums.priority.high);
Reactium.Zone.addMapper('zone-1', errorBoundaryMapper, Enums.priority.highest);const LazyComponent = React.lazy(() => import('./HeavyComponent'));
Reactium.Zone.addComponent({
id: 'LAZY-WIDGET',
component: (props) => (
<React.Suspense fallback={<Spinner />}>
<LazyComponent {...props} />
</React.Suspense>
),
zone: ['dashboard'],
});// Components can communicate via shared state
const useZoneState = (zone) => {
const [state, setState] = useState({});
useEffect(() => {
return Reactium.Zone.subscribe(zone, (components) => {
const newState = components.reduce((acc, comp) => ({
...acc,
[comp.id]: comp.state,
}), {});
setState(newState);
});
}, [zone]);
return state;
};// Filter based on user role
const roleFilter = (component) => {
const userRoles = Reactium.User.get('roles', []);
const requiredRole = component.requiredRole;
if (!requiredRole) return true;
return userRoles.includes(requiredRole);
};
// Filter based on feature flags
const featureFilter = (component) => {
if (!component.feature) return true;
return Reactium.Setting.get(`features.${component.feature}`, false);
};
Reactium.Zone.addFilter('admin-tools', roleFilter, Enums.priority.highest);
Reactium.Zone.addFilter('admin-tools', featureFilter, Enums.priority.high);Hook.register('zone-add-component', (registration) => {
console.log('Component added:', registration.id);
});
Hook.register('zone-update-component', (registration) => {
console.log('Component updated:', registration.id);
});
Hook.register('zone-remove-component', (registration) => {
console.log('Component removed:', registration.id);
});// Check if zone has specific component
const hasComponent = Reactium.Zone.hasZoneComponent('my-zone', 'MY-COMPONENT');
// Get specific component from zone
const component = Reactium.Zone.getZoneComponent('my-zone', 'MY-COMPONENT');
// Get all components (processed)
const components = Reactium.Zone.getZoneComponents('my-zone');
// Get all components (raw, unfiltered)
const rawComponents = Reactium.Zone.getZoneComponents('my-zone', true);- Check zone name spelling - Zone names are case-sensitive
- Verify component registration - If using string component reference
- Check filter functions - A filter might be excluding your component
- Verify plugin registration - Component might be added before plugin registers
- Check order conflicts - Very low order might push component out of view
- Profile zone updates - Use React DevTools Profiler
- Check filter complexity - Avoid async operations in filters
- Reduce subscriber count - Minimize components using
useZoneComponents - Use raw access - For read-only zone inspection
- Consider passthrough mode - For custom rendering control
- Unsubscribe properly - Always return unsubscribe function from
useEffect - Remove on unmount - Clean up zone components when plugin unregisters
- Check mapper closures - Avoid capturing large objects in mapper functions
- Always provide explicit IDs - Simplifies debugging, updates, and component removal
- Use capability checks at registration time - More performant than runtime filter-based access control
- Namespace component IDs - Prevents plugin collisions (e.g.,
PLUGIN-MYPLUGIN-WIDGET) - Use priority enums - More readable and maintainable than magic numbers
- Subscribe with cleanup - Always return unsubscribe function to prevent memory leaks
- Document custom zones - Help future developers understand zone architecture
- Consider memoization in Zone component - Could improve re-render performance for zones with many components
- Add zone registry inspection tooling - Developer experience improvement for debugging zone state
- Document filter/mapper performance characteristics - Set clear expectations for complex control functions
- Add mapper usage examples in core plugins - Current examples only exist in documentation
- Create zone performance profiling hook - Help developers identify zone-related bottlenecks
The Zone System provides:
- Decoupled Architecture: Plugins extend UI without modifying core
- Dynamic Composition: Components added/removed at runtime
- Flexible Control: Filters, mappers, and sorters customize behavior
- Reactive Updates: Subscription model for zone changes
- Performance: Efficient rendering with optimization options
Master these patterns to build extensible, maintainable Reactium applications.