Hooks
The SDK provides hooks for programmatic control over dynamic component loading, giving you full flexibility to build custom UI patterns.
useDynamicComponent
Load and manage a dynamic component programmatically. This hook provides access to the component, loading state, errors, and control functions.
Basic Usage
import { useDynamicComponent } from '@dpage-ai/react-native-dpage';
function MyScreen() {
const {
Component, // The loaded component (or null)
loading, // Boolean loading state
error, // Error object (if any)
reload, // Function to reload from cache
refresh, // Function to force refresh from API
} = useDynamicComponent('MyComponent', {
projectId: 'optional-override',
});
if (loading) return <LoadingSpinner />;
if (error) return <ErrorView error={error} onRetry={reload} />;
if (!Component) return null;
return <Component title="Hello" count={42} />;
}Return Values
| Prop | Type | Default | Description |
|---|---|---|---|
Component | React.ComponentType | null | - | The loaded component, ready to render |
loading | boolean | - | True while component is being loaded |
error | Error | null | - | Error if loading failed |
reload | () => void | - | Reload component (checks cache first) |
refresh | () => void | - | Force refresh from API (bypasses cache) |
Options
Keep overrides object identity stable
runtimeDepsOverrides, hoist it or create it via useMemo. A new object every render bypasses caching and recompiles the component.useDynamicComponent('ComponentName', {
projectId: 'override-project', // Optional project override
runtimeDepsOverrides: editModeDeps, // Optional per-render runtime deps overrides
});import React, { useMemo } from 'react';
import { useDynamicComponent } from '@dpage-ai/react-native-dpage';
function MyScreen() {
const editModeDeps = useMemo(
() => ({
View: EditableView,
Text: EditableText,
}),
[]
);
const { Component } = useDynamicComponent('MyComponent', {
runtimeDepsOverrides: editModeDeps,
});
return Component ? <Component /> : null;
}Custom Loading UI
Build completely custom loading experiences with the hook:
function PromoBanner() {
const { Component, loading, error, reload } = useDynamicComponent('PromoBanner');
if (loading) {
return (
<View style={styles.skeleton}>
<Animated.View style={[styles.shimmer, animatedStyle]} />
</View>
);
}
if (error || !Component) {
return (
<Pressable onPress={reload} style={styles.retry}>
<Text>Tap to retry</Text>
</Pressable>
);
}
return <Component />;
}Conditional Loading
Load components based on conditions:
function FeatureCard({ feature }: { feature: string }) {
const { Component, loading, error } = useDynamicComponent(`Feature_${feature}`);
// Skip if feature not available
if (error) return null;
if (loading) return <FeatureSkeleton />;
if (!Component) return null;
return <Component />;
}Refreshing Content
Use refresh to force fetch the latest version:
function RefreshableContent() {
const { Component, loading, refresh } = useDynamicComponent('LiveContent');
return (
<ScrollView
refreshControl={
<RefreshControl
refreshing={loading}
onRefresh={refresh}
/>
}
>
{Component && <Component />}
</ScrollView>
);
}Multiple Components
Load multiple components independently:
function Dashboard() {
const header = useDynamicComponent('DashboardHeader');
const stats = useDynamicComponent('DashboardStats');
const feed = useDynamicComponent('ActivityFeed');
const isLoading = header.loading || stats.loading || feed.loading;
if (isLoading) return <DashboardSkeleton />;
return (
<View>
{header.Component && <header.Component />}
{stats.Component && <stats.Component />}
{feed.Component && <feed.Component />}
</View>
);
}Error Handling Patterns
import { HttpError, FetchTimeoutError } from '@dpage-ai/react-native-dpage';
function RobustComponent() {
const { Component, error, reload } = useDynamicComponent('MyComponent');
if (error) {
if (error instanceof HttpError) {
if (error.status === 404) {
return <NotFoundView />;
}
if (error.status >= 500) {
return <ServerErrorView onRetry={reload} />;
}
}
if (error instanceof FetchTimeoutError) {
return <TimeoutView onRetry={reload} />;
}
return <GenericErrorView error={error} />;
}
return Component ? <Component /> : null;
}Best Practice
useNamedPersistentState
A useState-like hook for persistent state that survives app restarts. Use this for data that should be saved (scores, settings, drafts) instead of ephemeral UI state.
Basic Usage
import { useNamedPersistentState } from '@dpage-ai/react-native-dpage';
function Counter() {
// State persists across app restarts
const [count, setCount] = useNamedPersistentState('counter.value', 0);
return (
<View>
<Text>Count: {count}</Text>
<Button title="Increment" onPress={() => setCount(c => c + 1)} />
</View>
);
}Signature
function useNamedPersistentState<T>(
key: string, // Unique key for this state
initialValue: T // Default value if no persisted value exists
): [T, (v: T | ((prev: T) => T)) => void]When to Use
| Prop | Type | Default | Description |
|---|---|---|---|
useNamedPersistentState | Persistent | - | User progress, scores, settings, preferences, draft content, saved data |
useState | Ephemeral | - | Modal visibility, loading flags, temporary input, UI toggles |
Key Naming Conventions
// Use descriptive, namespaced keys
const [score, setScore] = useNamedPersistentState('game.score', 0);
const [theme, setTheme] = useNamedPersistentState('settings.theme', 'dark');
const [draft, setDraft] = useNamedPersistentState('editor.draft', '');
const [prefs, setPrefs] = useNamedPersistentState('user.preferences', {});Miniapp Isolation
Cloud Sync
State is persisted to AsyncStorage automatically. For cloud sync, use the SDK's sync callbacks:
import { PersistenceManager } from '@dpage-ai/react-native-dpage';
// Set up cloud sync callbacks
PersistenceManager.setSyncCallbacks({
// Called when miniapp closes (user navigates back)
onMiniappClose: async (miniappId) => {
const snapshot = await PersistenceManager.getSnapshot(miniappId);
if (snapshot) {
await uploadToCloud(miniappId, snapshot);
await PersistenceManager.markSynced(miniappId);
}
},
onAppForeground: async () => {
// Sync any unsynced changes from previous session
const unsynced = await PersistenceManager.getUnsyncedMiniapps();
// ... upload to cloud
},
});See the Persistent State docs for full cloud sync details.
Integration with Navigation
import { useFocusEffect } from '@react-navigation/native';
function DynamicScreen({ route }) {
const { screenName } = route.params;
const { Component, loading, refresh } = useDynamicComponent(screenName);
// Refresh when screen comes into focus
useFocusEffect(
useCallback(() => {
refresh();
}, [refresh])
);
if (loading) return <ScreenLoader />;
return Component ? <Component /> : <FallbackScreen />;
}