Updating Parcels
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.
Warning
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> );};