@mutalabs/react-native-muta
v1.2.1
Published
A React Native SDK for creating dynamic, remotely configurable onboarding experiences
Downloads
171
Maintainers
Readme
A React Native SDK for creating dynamic, remotely configurable onboarding experiences in your mobile app. Muta allows you to create, update, and A/B test your onboarding flows without deploying app updates or writing a single line of code.
Features
- 🎨 No-code editor - design and update flows with a drag-and-drop interface
- 🚀 Remote updates - modify onboarding flows instantly without app releases
- ✨ Rich components - buttons, text, images, shapes, icons, and more
- 🔄 Smooth transitions - fade and slide animations
- 📊 Analytics integration - track user behavior with any analytics provider
- 📝 User input collection - gather text and multiple choice responses
- 💪 Full TypeScript support
- 🪶 Extremely lightweight
Installation
# Install the package and its required dependencies
npm install @mutalabs/react-native-muta react-native-webview
# or
yarn add @mutalabs/react-native-muta react-native-webview
# For React Native CLI projects only (not needed for Expo)
cd ios && pod installNote: If you're using Expo:
- The pod install step is not required
- You cannot use Expo Go - you must create a fresh development build
- Follow the official Expo guide to create a development build: Creating Development Builds
Quick Start
- Add the MutaRoot component to your app:
import { MutaRoot, Muta } from '@mutalabs/react-native-muta';
function App() {
return (
<>
<MutaRoot apiKey="your-api-key" />
{/* Your app content */}
</>
);
}- Display a placement:
// Basic usage
Muta.displayPlacement({
placementId: 'your-placement-id',
bgColor: '#000000' // Should match your first placement screen's background color
})
// With custom loading screen and presentation type
Muta.displayPlacement({
placementId: 'your-placement-id',
bgColor: '#000000', // Should match your first placement screen's background color
loadingScreen: <YourCustomLoadingScreen />,
presentationType: 'fade' // 'slide' | 'fade' | 'none'
})API Reference
MutaRoot
The root component that initializes the SDK and manages placements.
<MutaRoot apiKey={string} />Muta.displayPlacement()
Displays a placement in a modal overlay.
interface DisplayPlacementOptions {
placementId: string
loadingScreen?: React.ReactNode
bgColor: string // Should match your first placement screen's background color
presentationType?: 'slide' | 'fade' | 'none'
injectedScreens?: Array<{
screenName: string
component: React.ComponentType<{
onContinue: () => void
onBack: () => void
variables: any[]
screenId: string
screenIndex: number
}>
}>
}
Muta.displayPlacement(options: DisplayPlacementOptions)Options
placementId(required): The ID of the placement to displaybgColor(required): Background color that matches your first placement screen. This creates a seamless transition between your app and the placement.loadingScreen(optional): A custom React component to show while the placement is loadingpresentationType(optional): Animation style when showing the placement. Defaults to 'slide'.'slide': Slides up from the bottom'fade': Fades in from transparent'none': No animation
injectedScreens(optional): Array of native React components to inject into code screens (see Code Screens section below)
Example App
import { Muta, MutaRoot } from '@mutalabs/react-native-muta';
function App() {
useEffect(() => {
// Show placement when needed
Muta.displayPlacement({
placementId: 'your-placement-id',
bgColor: '#1a1a1a', // Should match your first placement screen's background color
presentationType: 'fade' // Optional: customize the animation
});
}, []);
return (
<>
<MutaRoot apiKey="your-api-key" />
{/* Your app content */}
</>
);
}Custom Events
Muta allows you to emit custom events from your flows using the platform's behavior system. You can configure custom events in the Muta web editor and listen for them in your app to trigger specific actions.
Setting Up Custom Events
- In the Muta web editor, add an "Emit Event" behavior to any element
- Give your event a unique name (e.g.,
signup_started,premium_selected,survey_completed) - Optionally add custom data to pass along with the event
Listening for Custom Events
import { Muta } from '@mutalabs/react-native-muta';
function Onboarding() {
useEffect(() => {
// Listen for your custom events
const signupListener = Muta.on('signup_started', (event) => {
console.log('User started signup process');
// Trigger your signup flow
navigateToSignup();
});
const premiumListener = Muta.on('premium_selected', (event) => {
console.log('User selected premium option', event.eventData);
// Handle premium selection
setPremiumUser(true);
});
const surveyListener = Muta.on('survey_completed', (event) => {
console.log('Survey answers:', event.eventData);
// Save survey responses
saveSurveyData(event.eventData);
});
// Show the placement
Muta.displayPlacement({
placementId: 'your-placement-id',
bgColor: '#000000'
});
return () => {
signupListener.remove();
premiumListener.remove();
surveyListener.remove();
};
}, []);
return <View style={{ flex: 1 }} />;
}Custom Event Structure
{
type: string, // Your custom event name
timestamp: number, // Unix timestamp in ms
placementId: string, // ID of the placement
flowName?: string, // Name of the flow
screenIndex?: number, // Current screen index
eventData?: any // Custom data you configured in the editor
}Common Use Cases
- User Actions: Track when users click specific CTAs or make choices
- Navigation Control: Trigger app navigation based on flow interactions
- Data Collection: Gather form submissions or survey responses
- Feature Flags: Enable/disable features based on onboarding choices
- Conversion Tracking: Track specific conversion events for analytics
Code Screens
Code Screens allow you to inject native React components into your Muta flows. This enables you to combine remotely configured flows with custom native functionality like authentication forms, payment screens, or any complex native UI.
How It Works
- In the Muta web editor, add a "Code Screen" to your flow and give it a unique name (e.g., "Login Screen", "Payment Form")
- Configure the navigation destinations for continue and back actions
- In your app, pass the corresponding React component when displaying the placement
Example Usage
import { Muta } from '@mutalabs/react-native-muta';
import { LoginScreen } from './screens/LoginScreen';
import { PaymentScreen } from './screens/PaymentScreen';
// Display placement with injected native screens
Muta.displayPlacement({
placementId: 'onboarding',
bgColor: '#000000',
presentationType: 'none',
injectedScreens: [
{
screenName: 'Login Screen', // Must match the name in Muta editor
component: LoginScreen,
},
{
screenName: 'Payment Form', // Must match the name in Muta editor
component: PaymentScreen,
},
],
});Creating a Code Screen Component
Your component will receive props for navigation and flow data:
interface CodeScreenProps {
onContinue: () => void // Navigate to configured continue destination
onBack: () => void // Navigate to configured back destination
variables: any[] // Current flow variables
screenId: string // Unique screen ID
screenIndex: number // Screen position in flow
}
function LoginScreen({ onContinue, onBack, variables }: CodeScreenProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async () => {
try {
await loginUser(email, password);
onContinue(); // Navigate to next screen in flow
} catch (error) {
// Handle error
}
};
return (
<View style={styles.container}>
<TextInput
value={email}
onChangeText={setEmail}
placeholder="Email"
/>
<TextInput
value={password}
onChangeText={setPassword}
placeholder="Password"
secureTextEntry
/>
<Button title="Login" onPress={handleLogin} />
<Button title="Back" onPress={onBack} />
</View>
);
}Benefits
- Hybrid Flows: Combine no-code screens with custom native screens
- Seamless Transitions: Native screens animate in/out matching your flow animations
- Full Control: Access to all native capabilities while maintaining remote configurability
- Dynamic Routing: Configure navigation destinations remotely without code changes
Analytics Integration
Muta provides a flexible event system that works with any analytics provider (Mixpanel, AppsFlyer, Amplitude, Firebase, etc.). Events are emitted during key user interactions, allowing you to track and analyze your placement performance.
Tracking All Events
Use the wildcard '*' to listen to all events:
import { Muta } from '@mutalabs/react-native-muta'
// Example with Mixpanel
function MixpanelTracking() {
useEffect(() => {
const subscription = Muta.on('*', (event) => {
mixpanel.track(event.type, {
placement_id: event.placementId,
flow_name: event.flowName,
timestamp: event.timestamp,
...event, // includes all event-specific properties
})
})
return () => subscription.remove()
}, [])
}
// Example with AppsFlyer
function AppsFlyerTracking() {
useEffect(() => {
const subscription = Muta.on('*', (event) => {
appsflyer.logEvent(event.type, {
placement_id: event.placementId,
flow_name: event.flowName,
...event,
})
})
return () => subscription.remove()
}, [])
}Error Handling
Muta will automatically handle errors and prevent the flow from displaying if there are any issues. You can listen for these errors to show appropriate messages to your users.
Example Usage
import { Muta } from '@mutalabs/react-native-muta';
function Onboarding() {
useEffect(() => {
// Listen for error events
const subscription = Muta.on('error', (error) => {
if (error.type === 'network_error') {
// Handle network errors (e.g., show "No internet connection" message)
console.log('Network error:', error.message);
} else if (error.type === 'placement_error') {
// Handle placement errors (e.g., show "Invalid placement ID" message)
console.log('Placement error:', error.message);
}
});
// Show the placement
Muta.displayPlacement({
placementId: 'your-placement-id',
bgColor: '#000000'
});
return () => subscription.remove();
}, []);
return <View style={{ flex: 1 }} />;
}Error Types
Network Errors
- Emitted when there's no internet connection
- The flow will not display
- You should show an appropriate "No internet connection" message
Placement Errors
- Emitted when there's an issue with the placement ID or API key, typically this is when a key or placement does not match what is in the web app or has been deleted
- The flow will not display
- You should show an appropriate error message to the user
Error Event Structure
// Network Error
{
type: 'network_error',
message: string, // Error message
timestamp: number // Unix timestamp in ms
}
// Placement Error
{
type: 'placement_error',
message: string, // Error message
code: string, // Error code
timestamp: number, // Unix timestamp in ms
placementId: string // ID of the placement that failed
}Available Events
- Flow Started (
flow_started) Emitted when a placement flow begins displaying.
{
type: 'flow_started',
timestamp: number, // Unix timestamp in ms
placementId: string, // ID of the placement
flowName?: string, // Name of the flow (if configured)
totalScreens: number // Total number of screens in flow
}- Screen Viewed (
screen_viewed) Emitted when a user views a new screen in the flow.
{
type: 'screen_viewed',
timestamp: number,
placementId: string,
flowName?: string,
screenIndex: number, // Zero-based index of current screen
totalScreens: number, // Total number of screens in flow
screenName?: string // Name of the screen (if configured)
}- Flow Completed (
flow_completed) Emitted when a user successfully finishes the flow.
{
type: 'flow_completed',
timestamp: number, // Unix timestamp in ms
placementId: string, // ID of the placement
flowName: string, // Name of the flow
screenIndex: number, // Index of final screen
totalScreens: number, // Total screens in flow
screenName: string, // Name of final screen
eventData?: { // Contains all collected variables
variables: Record<string, any> // Variable IDs mapped to their values
}
}Variable Structure
When a flow completes, eventData.variables contains an object where:
- Key: Variable ID (e.g.,
var_1758310510757_as20wkeo4) - Value: Object containing:
id(string): Unique variable identifiername(string): Human-readable variable name from the Muta editortype(string): Variable type (e.g., "text")value(any): The collected valuedefaultValue(any): The default value
Example:
Muta.on('flow_completed', (event) => {
if (event.eventData?.variables) {
Object.entries(event.eventData.variables).forEach(([varId, varData]) => {
console.log(`Variable '${varData.name}': ${varData.value}`)
})
}
})Output:
Variable 'MultipleChoice1': Family
Variable 'MultipleChoice2': Fluent- Flow Abandoned (
flow_abandoned) Emitted when a user exits the flow before completion.
{
type: 'flow_abandoned',
timestamp: number,
placementId: string,
flowName?: string,
screenIndex: number, // Index of last viewed screen
totalScreens: number, // Total screens in flow
lastScreenIndex: number, // Index of last viewed screen
screenName?: string // Name of last viewed screen
}- Custom Events (configured in web editor) Emitted when user triggers actions configured with "Emit Event" behavior.
{
type: string, // Your custom event name
timestamp: number, // Unix timestamp in ms
placementId: string, // ID of the placement
flowName?: string, // Name of the flow
screenIndex?: number, // Current screen index
eventData?: any // Custom data configured in editor
}- Error Events (
error) Emitted when there are network or placement errors.
// Network Error
{
type: 'network_error',
message: string, // Error message
timestamp: number // Unix timestamp in ms
}
// Placement Error
{
type: 'placement_error',
message: string, // Error message
code: string, // Error code
timestamp: number, // Unix timestamp in ms
placementId: string // ID of the placement that failed
}Practical Example with Custom Events
Here's an example of using custom events to handle user interactions:
import { Muta } from '@mutalabs/react-native-muta'
function OnboardingWithCustomEvents() {
useEffect(() => {
// Listen for flow completion with all collected variables
const completionListener = Muta.on('flow_completed', (event) => {
// Access all variables collected during the flow
if (event.eventData?.variables) {
const { user_name, user_email, selected_plan, preferences } = event.eventData.variables
console.log('Flow completed with data:', {
name: user_name,
email: user_email,
plan: selected_plan,
preferences: preferences
})
// Save to your backend
saveOnboardingData(event.eventData.variables)
}
})
// Listen for form submission event
const formListener = Muta.on('form_submitted', (event) => {
// Access the form data from eventData
const { name, email, preferences } = event.eventData
console.log('User submitted:', { name, email, preferences })
// Save to your backend
saveUserProfile({ name, email, preferences })
})
// Listen for plan selection
const planListener = Muta.on('plan_selected', (event) => {
const { planType, price } = event.eventData
// Handle plan selection
if (planType === 'premium') {
navigateToPremiumSignup()
}
})
return () => {
completionListener.remove()
formListener.remove()
planListener.remove()
}
}, [])
return null
}