Updating parcels
Properties are useful for initial configuration when a Parcel is first mounted, but updating properties after mount will not trigger a remount or cause the Parcel to update by default.
This default behavior prevents common issues such as:
- State Loss: Internal state (form inputs, scroll positions, user interactions) would reset on every property change
- Performance Impact: Unmounting and remounting is computationally expensive, especially for complex Parcels
- UI Flickering: Users would experience visual flickering as components rebuild
Recommended: Event-based communication
For runtime communication between your host application and Parcels, use the event-based pattern:
In the Parcel: Listen for events using the useRegisterEventListener hook or registerEventListener tool
In the Host: Send updates using the dispatchEvent tool
This approach keeps the Parcel mounted, preserves its internal state, and updates only the necessary data.
Enabling property-based remounting
If you need property changes to trigger a full Parcel remount, you can enable the remounting behavior by setting the enableMutationObserver property to true in the settings.json file as shown here.
Note: This approach is not recommended for most use cases. The event-based communication pattern provides better performance and user experience.
Recommended: Event based communication example
Here's a complete example demonstrating how to use properties for initial configuration and events for runtime updates:
Parcel component with event listener
The Parcel initializes with the initial-count prop, then listens for runtime updates via the cartUpdate event.
// Example Parcel fileimport React, { useState, useCallback, useEffect } from 'react';import { Button } from '@uhg-abyss/web/ui/Button';import { createTheme } from '@uhg-abyss/web/tools/theme';import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';import { useRegisterEventListener } from '@uhg-abyss/parcels/hooks/useRegisterEventListener';
const theme = createTheme('uhc');
export const ParcelApp = (args) => { const [cartCount, setCartCount] = useState(args['initial-count']);
// Event handler for cart updates const handleCartUpdate = useCallback((event) => { setCartCount(event.detail.count); }, []);
/** * STORYBOOK DEVELOPMENT ONLY: * Syncs state with args['initial-count'] when Controls change. * Not needed in production because args never change at runtime. */ useEffect(() => { if ((window as any).__STORYBOOK_PREVIEW__) { setCartCount(args?.['initial-count']); } }, [args?.['initial-count']]);
// Listen for 'cartUpdate' events from the host useRegisterEventListener('cartUpdate', handleCartUpdate);
return ( <ThemeProvider theme={theme}> <Button>🛒 Cart Count: {cartCount}</Button> </ThemeProvider> );};Host application dispatching events
The host application sends updates to the Parcel using the dispatchEvent tool.
// Example host/web fileimport React, { useState } from 'react';import { dispatchEvent } from '@uhg-abyss/parcels/tools/dispatchEvent';import { Button } from '@uhg-abyss/web/ui/Button';import { Layout } from '@uhg-abyss/web/ui/Layout';
export const HelloAbyss = () => { const [cartCount, setCartCount] = useState(5);
const addToCart = () => { const newCount = cartCount + 1; setCartCount(newCount); dispatchEvent('cartUpdate', { count: newCount }); };
const removeFromCart = () => { const newCount = Math.max(cartCount - 1, 0); setCartCount(newCount); dispatchEvent('cartUpdate', { count: newCount }); };
return ( <Layout.Stack> <Button onClick={addToCart}>Add Item</Button> <Button onClick={removeFromCart}>Remove Item</Button> <abyss-parcel import="parcel-app@test" initial-count={5} /> </Layout.Stack> );};In this example:
- The Parcel is initialized with
initial-count={5} - As the user clicks the buttons, the host dispatches
cartUpdateevents - The Parcel's event listener updates its internal state without remounting
- User interactions and component state are preserved throughout
Updating via property changes
In this example, the Parcel depends entirely on the cart-count property for updates.
When the host changes the property value, the Parcel does not automatically update unless remounting is enabled (via enableMutationObserver).
Parcel component relying on property changes
// Example Parcel fileimport React from 'react';import { Button } from '@uhg-abyss/web/ui/Button';import { createTheme } from '@uhg-abyss/web/tools/theme';import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
const theme = createTheme('uhc');
export const ParcelApp = (args) => { const cartCount = args?.count;
return ( <ThemeProvider theme={theme}> <Button>🛒 Cart Count: {cartCount}</Button> </ThemeProvider> );};Host application attempting runtime updates via property changes
// Example host/web fileimport React, { useState } from 'react';import { Button } from '@uhg-abyss/web/ui/Button';import { Layout } from '@uhg-abyss/web/ui/Layout';
export const HelloAbyss = () => { const [cartCount, setCartCount] = useState(5);
const addToCart = () => { const newCount = cartCount + 1; setCartCount(newCount); };
return ( <Layout.Stack> <Button onClick={addToCart}>Add Item</Button> <abyss-parcel import="parcel-app@test" count={cartCount}></abyss-parcel> </Layout.Stack> );};