mixpanel-react-native 3.1.2 → 3.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +12 -1
- package/.github/dependabot.yml +7 -0
- package/.github/workflows/node.js.yml +24 -1
- package/.vscode/settings.json +2 -1
- package/MixpanelReactNative.podspec +1 -1
- package/Samples/MixpanelExample/ios/MixpanelExample.xcworkspace/contents.xcworkspacedata +10 -0
- package/Samples/MixpanelExample/ios/Podfile.lock +1996 -0
- package/Samples/MixpanelStarter/.bundle/config +2 -0
- package/Samples/MixpanelStarter/.env.example +4 -0
- package/Samples/MixpanelStarter/.eslintrc.js +4 -0
- package/Samples/MixpanelStarter/.prettierrc.js +5 -0
- package/Samples/MixpanelStarter/.watchmanconfig +1 -0
- package/Samples/MixpanelStarter/App.tsx +10 -0
- package/Samples/MixpanelStarter/CLAUDE.md +538 -0
- package/Samples/MixpanelStarter/Gemfile +16 -0
- package/Samples/MixpanelStarter/INTEGRATION_GUIDE.md +606 -0
- package/Samples/MixpanelStarter/README.md +406 -0
- package/Samples/MixpanelStarter/__tests__/MixpanelContext.test.tsx +63 -0
- package/Samples/MixpanelStarter/android/app/build.gradle +119 -0
- package/Samples/MixpanelStarter/android/app/debug.keystore +0 -0
- package/Samples/MixpanelStarter/android/app/proguard-rules.pro +10 -0
- package/Samples/MixpanelStarter/android/app/src/main/AndroidManifest.xml +27 -0
- package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainActivity.kt +22 -0
- package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainApplication.kt +27 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/values/strings.xml +3 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/values/styles.xml +9 -0
- package/Samples/MixpanelStarter/android/build.gradle +21 -0
- package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/Samples/MixpanelStarter/android/gradle.properties +44 -0
- package/Samples/MixpanelStarter/android/gradlew +251 -0
- package/Samples/MixpanelStarter/android/gradlew.bat +99 -0
- package/Samples/MixpanelStarter/android/settings.gradle +6 -0
- package/Samples/MixpanelStarter/app.json +4 -0
- package/Samples/MixpanelStarter/babel.config.js +14 -0
- package/Samples/MixpanelStarter/index.js +9 -0
- package/Samples/MixpanelStarter/ios/.xcode.env +11 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/AppDelegate.swift +48 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/Contents.json +6 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/Info.plist +55 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/LaunchScreen.storyboard +47 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/PrivacyInfo.xcprivacy +38 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/project.pbxproj +482 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/xcshareddata/xcschemes/MixpanelStarter.xcscheme +88 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter.xcworkspace/contents.xcworkspacedata +10 -0
- package/Samples/MixpanelStarter/ios/Podfile +34 -0
- package/Samples/MixpanelStarter/ios/Podfile.lock +2839 -0
- package/Samples/MixpanelStarter/jest.config.js +3 -0
- package/Samples/MixpanelStarter/metro.config.js +42 -0
- package/Samples/MixpanelStarter/package-lock.json +12141 -0
- package/Samples/MixpanelStarter/package.json +51 -0
- package/Samples/MixpanelStarter/src/@types/env.d.ts +3 -0
- package/Samples/MixpanelStarter/src/App.tsx +83 -0
- package/Samples/MixpanelStarter/src/components/ActionButton.tsx +92 -0
- package/Samples/MixpanelStarter/src/components/ErrorBoundary.tsx +81 -0
- package/Samples/MixpanelStarter/src/components/EventTrackingLog.tsx +163 -0
- package/Samples/MixpanelStarter/src/components/FlagCard.tsx +199 -0
- package/Samples/MixpanelStarter/src/components/InfoCard.tsx +77 -0
- package/Samples/MixpanelStarter/src/components/TestResultDisplay.tsx +181 -0
- package/Samples/MixpanelStarter/src/constants/tracking.ts +77 -0
- package/Samples/MixpanelStarter/src/contexts/MixpanelContext.tsx +159 -0
- package/Samples/MixpanelStarter/src/screens/FeatureFlagsScreen.tsx +1011 -0
- package/Samples/MixpanelStarter/src/screens/HomeScreen.tsx +307 -0
- package/Samples/MixpanelStarter/src/screens/OnboardingScreen.tsx +253 -0
- package/Samples/MixpanelStarter/src/screens/SettingsScreen.tsx +316 -0
- package/Samples/MixpanelStarter/src/types/flags.types.ts +42 -0
- package/Samples/MixpanelStarter/src/types/mixpanel.types.ts +26 -0
- package/Samples/MixpanelStarter/tsconfig.json +13 -0
- package/__tests__/flags.test.js +730 -0
- package/__tests__/index.test.js +7 -3
- package/__tests__/jest_setup.js +18 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/mixpanel/reactnative/MixpanelReactNativeModule.java +272 -2
- package/index.d.ts +64 -1
- package/index.js +42 -3
- package/ios/MixpanelReactNative.m +19 -1
- package/ios/MixpanelReactNative.swift +183 -5
- package/javascript/mixpanel-flags-js.js +463 -0
- package/javascript/mixpanel-flags.js +290 -0
- package/javascript/mixpanel-main.js +13 -1
- package/package.json +2 -2
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# MixpanelStarter
|
|
2
|
+
|
|
3
|
+
A modern, production-ready React Native sample application demonstrating [Mixpanel React Native SDK](https://github.com/mixpanel/mixpanel-react-native) integration with TypeScript, Context API, and best practices.
|
|
4
|
+
|
|
5
|
+
## 🎯 Purpose
|
|
6
|
+
|
|
7
|
+
This sample app bridges the gap between basic "Hello World" examples and complex production implementations. It demonstrates **80% of common Mixpanel use cases** with clean, maintainable patterns you can copy directly into your app.
|
|
8
|
+
|
|
9
|
+
### What Makes This Different
|
|
10
|
+
|
|
11
|
+
- **Modern Architecture**: Context API + TypeScript strict mode
|
|
12
|
+
- **Production Patterns**: Error handling, loading states, proper lifecycle management
|
|
13
|
+
- **Educational**: Each screen explains what's happening and why
|
|
14
|
+
- **Copy-Paste Ready**: Well-commented code you can use immediately
|
|
15
|
+
|
|
16
|
+
## ✨ Features Demonstrated
|
|
17
|
+
|
|
18
|
+
This app showcases 9 core Mixpanel SDK capabilities:
|
|
19
|
+
|
|
20
|
+
| Feature | Description | Screen |
|
|
21
|
+
|---------|-------------|--------|
|
|
22
|
+
| 🆔 **User Identification** | `identify()`, `alias()`, anonymous-to-identified flow | Onboarding |
|
|
23
|
+
| 👤 **User Profiles** | `getPeople().set()`, `setOnce()` for profile properties | Onboarding |
|
|
24
|
+
| 📊 **Event Tracking** | `track()` with custom properties and metadata | Home |
|
|
25
|
+
| ⏱️ **Timed Events** | `timeEvent()` for automatic duration tracking | Home |
|
|
26
|
+
| 🌐 **Super Properties** | `registerSuperProperties()` for global context | Home |
|
|
27
|
+
| 🚩 **Feature Flags** | `flags.loadFlags()`, `isEnabled()`, dynamic feature control - **FULL INTEGRATION TEST SUITE** | Feature Flags |
|
|
28
|
+
| 🔒 **Privacy Controls** | `optIn/OutTracking()` for GDPR compliance | Settings |
|
|
29
|
+
| 🗑️ **Data Management** | `reset()` for logout/data deletion | Settings |
|
|
30
|
+
| 🚀 **Manual Flush** | `flush()` to force send queued events | Settings |
|
|
31
|
+
|
|
32
|
+
## 📱 App Structure
|
|
33
|
+
|
|
34
|
+
The app has 4 tabs:
|
|
35
|
+
|
|
36
|
+
1. **Onboarding (User ID Tab)**: Demonstrates user identification lifecycle
|
|
37
|
+
2. **Home (Events Tab)**: Shows event tracking and super properties
|
|
38
|
+
3. **Feature Flags**: **Comprehensive integration test suite** - exercises all 8 public API methods, Promise/Callback patterns, edge cases, and type coercion
|
|
39
|
+
4. **Settings**: Privacy controls and data management
|
|
40
|
+
|
|
41
|
+
## 🚀 Quick Start
|
|
42
|
+
|
|
43
|
+
### Prerequisites
|
|
44
|
+
|
|
45
|
+
- Node.js >= 20
|
|
46
|
+
- React Native development environment set up ([guide](https://reactnative.dev/docs/environment-setup))
|
|
47
|
+
- Mixpanel project token ([get one here](https://mixpanel.com/register))
|
|
48
|
+
|
|
49
|
+
### Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 1. Navigate to this directory
|
|
53
|
+
cd Samples/MixpanelStarter
|
|
54
|
+
|
|
55
|
+
# 2. Install dependencies
|
|
56
|
+
npm install
|
|
57
|
+
|
|
58
|
+
# 3. Install iOS dependencies (macOS only)
|
|
59
|
+
cd ios && pod install && cd ..
|
|
60
|
+
|
|
61
|
+
# 4. Configure your Mixpanel token
|
|
62
|
+
cp .env.example .env
|
|
63
|
+
# Edit .env and add your token: MIXPANEL_TOKEN=your_token_here
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Running the App
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# iOS
|
|
70
|
+
npm run ios
|
|
71
|
+
|
|
72
|
+
# Android
|
|
73
|
+
npm run android
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Note**: If you don't set a token in `.env`, the app will use a placeholder token. You'll see events in the console but they won't reach Mixpanel.
|
|
77
|
+
|
|
78
|
+
## 🏗️ Project Structure
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
MixpanelStarter/
|
|
82
|
+
├── src/
|
|
83
|
+
│ ├── contexts/
|
|
84
|
+
│ │ └── MixpanelContext.tsx # Context Provider + useMixpanel hook
|
|
85
|
+
│ ├── screens/
|
|
86
|
+
│ │ ├── OnboardingScreen.tsx # User identification demos
|
|
87
|
+
│ │ ├── HomeScreen.tsx # Event tracking patterns
|
|
88
|
+
│ │ ├── FeatureFlagsScreen.tsx # Feature flags integration tests
|
|
89
|
+
│ │ └── SettingsScreen.tsx # Privacy & data management
|
|
90
|
+
│ ├── components/
|
|
91
|
+
│ │ ├── ActionButton.tsx # Reusable button component
|
|
92
|
+
│ │ ├── InfoCard.tsx # Info display card
|
|
93
|
+
│ │ ├── FlagCard.tsx # Flag display component
|
|
94
|
+
│ │ ├── TestResultDisplay.tsx # Test results visualization
|
|
95
|
+
│ │ ├── EventTrackingLog.tsx # Event history component
|
|
96
|
+
│ │ └── ErrorBoundary.tsx # Error handling wrapper
|
|
97
|
+
│ ├── types/
|
|
98
|
+
│ │ ├── mixpanel.types.ts # TypeScript definitions
|
|
99
|
+
│ │ └── flags.types.ts # Feature flags test types
|
|
100
|
+
│ ├── constants/
|
|
101
|
+
│ │ └── tracking.ts # Event names & properties
|
|
102
|
+
│ └── App.tsx # Navigation setup
|
|
103
|
+
├── __tests__/
|
|
104
|
+
│ └── MixpanelContext.test.tsx # Basic context tests
|
|
105
|
+
└── .env.example # Environment template
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 📖 Key Patterns (Copy These!)
|
|
109
|
+
|
|
110
|
+
### 1. Context Setup
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Wrap your app root with MixpanelProvider
|
|
114
|
+
<MixpanelProvider token="YOUR_TOKEN" trackAutomaticEvents={true}>
|
|
115
|
+
<App />
|
|
116
|
+
</MixpanelProvider>
|
|
117
|
+
|
|
118
|
+
// Use in any component
|
|
119
|
+
const { mixpanel, isInitialized, track, identify } = useMixpanel();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Why?** Provides app-wide access without prop drilling. Handles initialization and loading states automatically.
|
|
123
|
+
|
|
124
|
+
### 2. Event Tracking with Constants
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Define event names as constants
|
|
128
|
+
const Events = {
|
|
129
|
+
PRODUCT_VIEWED: 'Product Viewed',
|
|
130
|
+
VIDEO_COMPLETED: 'Video Completed',
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Track events
|
|
134
|
+
track(Events.PRODUCT_VIEWED, {
|
|
135
|
+
product_id: 'prod-123',
|
|
136
|
+
product_name: 'Sample Product',
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Why?** Constants prevent typos, enable autocomplete, and make refactoring easier.
|
|
142
|
+
|
|
143
|
+
### 3. User Identification Flow
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Get the current anonymous ID
|
|
147
|
+
const previousId = await mixpanel.getDistinctId();
|
|
148
|
+
|
|
149
|
+
// Identify the user
|
|
150
|
+
identify(userId);
|
|
151
|
+
|
|
152
|
+
// Link previous anonymous events
|
|
153
|
+
alias(userId, previousId);
|
|
154
|
+
|
|
155
|
+
// Set profile properties
|
|
156
|
+
mixpanel.getPeople().set({
|
|
157
|
+
$email: userId,
|
|
158
|
+
signup_date: new Date().toISOString(),
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Why?** Preserves event history when users transition from anonymous to identified.
|
|
163
|
+
|
|
164
|
+
### 4. Super Properties for Global Context
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Set once for all events
|
|
168
|
+
mixpanel.registerSuperProperties({
|
|
169
|
+
'App Version': '1.0.0',
|
|
170
|
+
'Dark Mode': darkModeEnabled,
|
|
171
|
+
'Platform': Platform.OS,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// These are automatically included in every event
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Why?** Eliminates repetitive property passing. Perfect for user preferences and app state.
|
|
178
|
+
|
|
179
|
+
### 5. Feature Flags for Dynamic Control
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Enable feature flags during initialization
|
|
183
|
+
await mixpanel.init(false, {}, undefined, false, {
|
|
184
|
+
enabled: true,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Load flags from Mixpanel
|
|
188
|
+
await mixpanel.flags.loadFlags();
|
|
189
|
+
|
|
190
|
+
// Check if flags are ready
|
|
191
|
+
const ready = mixpanel.flags.areFlagsReady();
|
|
192
|
+
|
|
193
|
+
// Synchronous flag evaluation (fast, uses cached values)
|
|
194
|
+
const isEnabled = mixpanel.flags.isEnabledSync('feature-key', false);
|
|
195
|
+
const value = mixpanel.flags.getVariantValueSync('feature-key', 'default');
|
|
196
|
+
|
|
197
|
+
// Asynchronous flag evaluation (ensures latest values)
|
|
198
|
+
const enabled = await mixpanel.flags.isEnabled('feature-key', false);
|
|
199
|
+
const variant = await mixpanel.flags.getVariant('feature-key', {
|
|
200
|
+
key: 'feature-key',
|
|
201
|
+
value: null,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Why?** Enables remote feature control, A/B testing, and gradual rollouts without app updates. Perfect for experimentation and user segmentation.
|
|
207
|
+
|
|
208
|
+
#### Feature Flags Testing Screen
|
|
209
|
+
|
|
210
|
+
The Feature Flags screen is a **full integration test suite** with:
|
|
211
|
+
|
|
212
|
+
- **4 Test Modes**: Sync, Async (Promise), Edge Cases, Type Coercion
|
|
213
|
+
- **12 Pre-configured Flags**: Boolean gates, string experiments, dynamic configs
|
|
214
|
+
- **Real-time Results**: Execution time, type detection, fallback tracking
|
|
215
|
+
- **Event Monitoring**: Live `$experiment_started` event tracking
|
|
216
|
+
- **All API Methods**: getVariantSync, getVariantValueSync, isEnabledSync, getVariant, getVariantValue, isEnabled (both Promise and Callback patterns)
|
|
217
|
+
|
|
218
|
+
Use this screen during development to verify Feature Flags functionality!
|
|
219
|
+
|
|
220
|
+
### 6. GDPR-Compliant Logout
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Before logout
|
|
224
|
+
track('User Logged Out');
|
|
225
|
+
await flush(); // Send pending events
|
|
226
|
+
reset(); // Clear all data
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Why?** Ensures data is sent before clearing, and respects user privacy.
|
|
230
|
+
|
|
231
|
+
## 🎓 Learning Resources
|
|
232
|
+
|
|
233
|
+
### In-App Learning
|
|
234
|
+
|
|
235
|
+
Each screen includes a "What's Happening?" card that explains:
|
|
236
|
+
- Which SDK methods are being used
|
|
237
|
+
- Why you'd use this pattern
|
|
238
|
+
- What data is being sent to Mixpanel
|
|
239
|
+
|
|
240
|
+
### Code Comments
|
|
241
|
+
|
|
242
|
+
Every key function includes comments explaining:
|
|
243
|
+
- **What** it does
|
|
244
|
+
- **Why** you'd use this pattern
|
|
245
|
+
- **When** to apply it in your app
|
|
246
|
+
|
|
247
|
+
## 🧪 Testing
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Run tests
|
|
251
|
+
npm test
|
|
252
|
+
|
|
253
|
+
# Run with coverage
|
|
254
|
+
npm test -- --coverage
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 🔧 Configuration
|
|
258
|
+
|
|
259
|
+
### Environment Variables
|
|
260
|
+
|
|
261
|
+
Create a `.env` file (see `.env.example`):
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
MIXPANEL_TOKEN=your_project_token_here
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Mixpanel Options
|
|
268
|
+
|
|
269
|
+
In `src/App.tsx`, you can configure:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
<MixpanelProvider
|
|
273
|
+
token={token}
|
|
274
|
+
trackAutomaticEvents={true} // Track app lifecycle events
|
|
275
|
+
useNative={true} // Use native iOS/Android SDKs
|
|
276
|
+
>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## 📚 Next Steps
|
|
280
|
+
|
|
281
|
+
### For Learning
|
|
282
|
+
|
|
283
|
+
1. Open the app and explore each screen
|
|
284
|
+
2. Read the "What's Happening?" cards to understand each pattern
|
|
285
|
+
3. Check Mixpanel dashboard to see events arrive
|
|
286
|
+
4. Modify the code and observe changes
|
|
287
|
+
|
|
288
|
+
### For Integration
|
|
289
|
+
|
|
290
|
+
1. Copy the `contexts/MixpanelContext.tsx` pattern into your app
|
|
291
|
+
2. Define your event names in `constants/tracking.ts`
|
|
292
|
+
3. Use `useMixpanel()` hook in your components
|
|
293
|
+
4. See [INTEGRATION_GUIDE.md](./INTEGRATION_GUIDE.md) for step-by-step instructions
|
|
294
|
+
|
|
295
|
+
## 🤔 Common Questions
|
|
296
|
+
|
|
297
|
+
### Q: Which events should I track?
|
|
298
|
+
|
|
299
|
+
**A:** Focus on user actions that indicate value or progress:
|
|
300
|
+
- Key conversions (signup, purchase, subscription)
|
|
301
|
+
- Feature usage (search, filter, share)
|
|
302
|
+
- Content engagement (view, complete, like)
|
|
303
|
+
|
|
304
|
+
Avoid tracking:
|
|
305
|
+
- Every button click (too noisy)
|
|
306
|
+
- Internal state changes (not user-facing)
|
|
307
|
+
- PII without consent (privacy violation)
|
|
308
|
+
|
|
309
|
+
### Q: When should I use super properties vs. event properties?
|
|
310
|
+
|
|
311
|
+
**A:**
|
|
312
|
+
- **Super Properties**: User preferences, app state, device info (changes infrequently, applies to all events)
|
|
313
|
+
- **Event Properties**: Action-specific data (product ID, video title, search query)
|
|
314
|
+
|
|
315
|
+
### Q: How do I test without sending real events?
|
|
316
|
+
|
|
317
|
+
**A:**
|
|
318
|
+
1. Use a test project token from Mixpanel
|
|
319
|
+
2. Enable logging: `mixpanel.setLoggingEnabled(true)`
|
|
320
|
+
3. Check console for event details
|
|
321
|
+
4. Use opt-out during development: `mixpanel.optOutTracking()`
|
|
322
|
+
|
|
323
|
+
### Q: What's the difference between native and JavaScript mode?
|
|
324
|
+
|
|
325
|
+
**A:**
|
|
326
|
+
- **Native (default)**: Uses iOS Swift SDK and Android Java SDK. Better performance, automatic properties.
|
|
327
|
+
- **JavaScript**: Pure JS implementation. Required for Expo and web. More portable but fewer automatic properties.
|
|
328
|
+
|
|
329
|
+
Set in `MixpanelProvider`: `useNative={true}` or `useNative={false}`
|
|
330
|
+
|
|
331
|
+
## 🐛 Troubleshooting
|
|
332
|
+
|
|
333
|
+
### Events not appearing in Mixpanel?
|
|
334
|
+
|
|
335
|
+
1. Check token is correct: `getDistinctId()` should return a value
|
|
336
|
+
2. Verify network connectivity
|
|
337
|
+
3. Check opt-out status: `hasOptedOutTracking()`
|
|
338
|
+
4. Manually flush: `flush()` to send immediately
|
|
339
|
+
5. Enable logging: `setLoggingEnabled(true)`
|
|
340
|
+
|
|
341
|
+
### TypeScript errors?
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
# Clear caches
|
|
345
|
+
npm start -- --reset-cache
|
|
346
|
+
|
|
347
|
+
# Rebuild
|
|
348
|
+
cd ios && pod install && cd ..
|
|
349
|
+
npm run ios
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Build errors?
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Clean iOS build
|
|
356
|
+
cd ios && xcodebuild clean && cd ..
|
|
357
|
+
|
|
358
|
+
# Clean Android build
|
|
359
|
+
cd android && ./gradlew clean && cd ..
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 📝 Design Decisions
|
|
363
|
+
|
|
364
|
+
### Why Context API over Redux/MobX?
|
|
365
|
+
|
|
366
|
+
- **Simplicity**: No boilerplate, minimal concepts
|
|
367
|
+
- **Standards**: Built into React, widely understood
|
|
368
|
+
- **Sufficient**: Mixpanel state is simple (one instance, few methods)
|
|
369
|
+
|
|
370
|
+
### Why TypeScript strict mode?
|
|
371
|
+
|
|
372
|
+
- **Safety**: Catches errors at compile time
|
|
373
|
+
- **Documentation**: Types serve as inline docs
|
|
374
|
+
- **Refactoring**: IDE support for safe code changes
|
|
375
|
+
|
|
376
|
+
### Why Bottom Tabs over Stack Navigation?
|
|
377
|
+
|
|
378
|
+
- **Clarity**: Each tab demonstrates a distinct concept
|
|
379
|
+
- **Simplicity**: Fewer navigation concepts to learn
|
|
380
|
+
- **Focus**: User isn't distracted by complex flows
|
|
381
|
+
|
|
382
|
+
## 🤝 Contributing
|
|
383
|
+
|
|
384
|
+
Found an issue or want to improve this sample?
|
|
385
|
+
|
|
386
|
+
1. This is a reference implementation, so changes should be broadly applicable
|
|
387
|
+
2. Keep patterns simple and well-commented
|
|
388
|
+
3. Ensure TypeScript strict mode passes
|
|
389
|
+
4. Test on both iOS and Android
|
|
390
|
+
|
|
391
|
+
## 📄 License
|
|
392
|
+
|
|
393
|
+
This sample app follows the same license as the [Mixpanel React Native SDK](https://github.com/mixpanel/mixpanel-react-native).
|
|
394
|
+
|
|
395
|
+
## 🔗 Resources
|
|
396
|
+
|
|
397
|
+
- [Mixpanel React Native SDK Docs](https://docs.mixpanel.com/docs/tracking/advanced/react-native)
|
|
398
|
+
- [Mixpanel Best Practices](https://docs.mixpanel.com/docs/tracking/how-tos/events-and-properties)
|
|
399
|
+
- [React Native Docs](https://reactnative.dev/)
|
|
400
|
+
- [React Navigation Docs](https://reactnavigation.org/)
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
**Made with ❤️ to help you integrate Mixpanel quickly and correctly.**
|
|
405
|
+
|
|
406
|
+
Questions? Check out the [Integration Guide](./INTEGRATION_GUIDE.md) or [Mixpanel Community](https://community.mixpanel.com/).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {render, waitFor} from '@testing-library/react-native';
|
|
3
|
+
import {Text} from 'react-native';
|
|
4
|
+
import {MixpanelProvider, useMixpanel} from '../src/contexts/MixpanelContext';
|
|
5
|
+
|
|
6
|
+
// Mock the Mixpanel SDK
|
|
7
|
+
jest.mock('mixpanel-react-native', () => ({
|
|
8
|
+
Mixpanel: jest.fn().mockImplementation(() => ({
|
|
9
|
+
init: jest.fn().mockResolvedValue(undefined),
|
|
10
|
+
registerSuperProperties: jest.fn(),
|
|
11
|
+
setLoggingEnabled: jest.fn(),
|
|
12
|
+
track: jest.fn(),
|
|
13
|
+
identify: jest.fn(),
|
|
14
|
+
alias: jest.fn(),
|
|
15
|
+
reset: jest.fn(),
|
|
16
|
+
flush: jest.fn().mockResolvedValue(undefined),
|
|
17
|
+
})),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Test component that uses the hook
|
|
21
|
+
const TestComponent = () => {
|
|
22
|
+
const {isInitialized, isLoading, error} = useMixpanel();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<Text testID="initialized">{isInitialized ? 'true' : 'false'}</Text>
|
|
27
|
+
<Text testID="loading">{isLoading ? 'true' : 'false'}</Text>
|
|
28
|
+
<Text testID="error">{error ? error.message : 'none'}</Text>
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('MixpanelContext', () => {
|
|
34
|
+
it('should provide Mixpanel context to children', async () => {
|
|
35
|
+
const {getByTestId} = render(
|
|
36
|
+
<MixpanelProvider token="test-token">
|
|
37
|
+
<TestComponent />
|
|
38
|
+
</MixpanelProvider>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Initially loading
|
|
42
|
+
expect(getByTestId('loading').props.children).toBe('true');
|
|
43
|
+
expect(getByTestId('initialized').props.children).toBe('false');
|
|
44
|
+
|
|
45
|
+
// Wait for initialization
|
|
46
|
+
await waitFor(() => {
|
|
47
|
+
expect(getByTestId('initialized').props.children).toBe('true');
|
|
48
|
+
expect(getByTestId('loading').props.children).toBe('false');
|
|
49
|
+
expect(getByTestId('error').props.children).toBe('none');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should throw error when used outside provider', () => {
|
|
54
|
+
// Suppress console.error for this test
|
|
55
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation();
|
|
56
|
+
|
|
57
|
+
expect(() => {
|
|
58
|
+
render(<TestComponent />);
|
|
59
|
+
}).toThrow('useMixpanel must be used within a MixpanelProvider');
|
|
60
|
+
|
|
61
|
+
consoleError.mockRestore();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
apply plugin: "com.android.application"
|
|
2
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
3
|
+
apply plugin: "com.facebook.react"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This is the configuration block to customize your React Native Android app.
|
|
7
|
+
* By default you don't need to apply any configuration, just uncomment the lines you need.
|
|
8
|
+
*/
|
|
9
|
+
react {
|
|
10
|
+
/* Folders */
|
|
11
|
+
// The root of your project, i.e. where "package.json" lives. Default is '../..'
|
|
12
|
+
// root = file("../../")
|
|
13
|
+
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
|
|
14
|
+
// reactNativeDir = file("../../node_modules/react-native")
|
|
15
|
+
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
|
|
16
|
+
// codegenDir = file("../../node_modules/@react-native/codegen")
|
|
17
|
+
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
|
|
18
|
+
// cliFile = file("../../node_modules/react-native/cli.js")
|
|
19
|
+
|
|
20
|
+
/* Variants */
|
|
21
|
+
// The list of variants to that are debuggable. For those we're going to
|
|
22
|
+
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
|
|
23
|
+
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
|
|
24
|
+
// debuggableVariants = ["liteDebug", "prodDebug"]
|
|
25
|
+
|
|
26
|
+
/* Bundling */
|
|
27
|
+
// A list containing the node command and its flags. Default is just 'node'.
|
|
28
|
+
// nodeExecutableAndArgs = ["node"]
|
|
29
|
+
//
|
|
30
|
+
// The command to run when bundling. By default is 'bundle'
|
|
31
|
+
// bundleCommand = "ram-bundle"
|
|
32
|
+
//
|
|
33
|
+
// The path to the CLI configuration file. Default is empty.
|
|
34
|
+
// bundleConfig = file(../rn-cli.config.js)
|
|
35
|
+
//
|
|
36
|
+
// The name of the generated asset file containing your JS bundle
|
|
37
|
+
// bundleAssetName = "MyApplication.android.bundle"
|
|
38
|
+
//
|
|
39
|
+
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
|
|
40
|
+
// entryFile = file("../js/MyApplication.android.js")
|
|
41
|
+
//
|
|
42
|
+
// A list of extra flags to pass to the 'bundle' commands.
|
|
43
|
+
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
|
|
44
|
+
// extraPackagerArgs = []
|
|
45
|
+
|
|
46
|
+
/* Hermes Commands */
|
|
47
|
+
// The hermes compiler command to run. By default it is 'hermesc'
|
|
48
|
+
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
|
|
49
|
+
//
|
|
50
|
+
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
|
51
|
+
// hermesFlags = ["-O", "-output-source-map"]
|
|
52
|
+
|
|
53
|
+
/* Autolinking */
|
|
54
|
+
autolinkLibrariesWithApp()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
|
59
|
+
*/
|
|
60
|
+
def enableProguardInReleaseBuilds = false
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The preferred build flavor of JavaScriptCore (JSC)
|
|
64
|
+
*
|
|
65
|
+
* For example, to use the international variant, you can use:
|
|
66
|
+
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
|
|
67
|
+
*
|
|
68
|
+
* The international variant includes ICU i18n library and necessary data
|
|
69
|
+
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
|
70
|
+
* give correct results when using with locales other than en-US. Note that
|
|
71
|
+
* this variant is about 6MiB larger per architecture than default.
|
|
72
|
+
*/
|
|
73
|
+
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
|
74
|
+
|
|
75
|
+
android {
|
|
76
|
+
ndkVersion rootProject.ext.ndkVersion
|
|
77
|
+
buildToolsVersion rootProject.ext.buildToolsVersion
|
|
78
|
+
compileSdk rootProject.ext.compileSdkVersion
|
|
79
|
+
|
|
80
|
+
namespace "com.mixpanelstarter"
|
|
81
|
+
defaultConfig {
|
|
82
|
+
applicationId "com.mixpanelstarter"
|
|
83
|
+
minSdkVersion rootProject.ext.minSdkVersion
|
|
84
|
+
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
85
|
+
versionCode 1
|
|
86
|
+
versionName "1.0"
|
|
87
|
+
}
|
|
88
|
+
signingConfigs {
|
|
89
|
+
debug {
|
|
90
|
+
storeFile file('debug.keystore')
|
|
91
|
+
storePassword 'android'
|
|
92
|
+
keyAlias 'androiddebugkey'
|
|
93
|
+
keyPassword 'android'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
buildTypes {
|
|
97
|
+
debug {
|
|
98
|
+
signingConfig signingConfigs.debug
|
|
99
|
+
}
|
|
100
|
+
release {
|
|
101
|
+
// Caution! In production, you need to generate your own keystore file.
|
|
102
|
+
// see https://reactnative.dev/docs/signed-apk-android.
|
|
103
|
+
signingConfig signingConfigs.debug
|
|
104
|
+
minifyEnabled enableProguardInReleaseBuilds
|
|
105
|
+
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
dependencies {
|
|
111
|
+
// The version of react-native is set by the React Native Gradle Plugin
|
|
112
|
+
implementation("com.facebook.react:react-android")
|
|
113
|
+
|
|
114
|
+
if (hermesEnabled.toBoolean()) {
|
|
115
|
+
implementation("com.facebook.react:hermes-android")
|
|
116
|
+
} else {
|
|
117
|
+
implementation jscFlavor
|
|
118
|
+
}
|
|
119
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Add project specific ProGuard rules here.
|
|
2
|
+
# By default, the flags in this file are appended to flags specified
|
|
3
|
+
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
|
4
|
+
# You can edit the include path and order by changing the proguardFiles
|
|
5
|
+
# directive in build.gradle.
|
|
6
|
+
#
|
|
7
|
+
# For more details, see
|
|
8
|
+
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
9
|
+
|
|
10
|
+
# Add any project specific keep options here:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
|
|
5
|
+
<application
|
|
6
|
+
android:name=".MainApplication"
|
|
7
|
+
android:label="@string/app_name"
|
|
8
|
+
android:icon="@mipmap/ic_launcher"
|
|
9
|
+
android:roundIcon="@mipmap/ic_launcher_round"
|
|
10
|
+
android:allowBackup="false"
|
|
11
|
+
android:theme="@style/AppTheme"
|
|
12
|
+
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
|
13
|
+
android:supportsRtl="true">
|
|
14
|
+
<activity
|
|
15
|
+
android:name=".MainActivity"
|
|
16
|
+
android:label="@string/app_name"
|
|
17
|
+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
|
18
|
+
android:launchMode="singleTask"
|
|
19
|
+
android:windowSoftInputMode="adjustResize"
|
|
20
|
+
android:exported="true">
|
|
21
|
+
<intent-filter>
|
|
22
|
+
<action android:name="android.intent.action.MAIN" />
|
|
23
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
24
|
+
</intent-filter>
|
|
25
|
+
</activity>
|
|
26
|
+
</application>
|
|
27
|
+
</manifest>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.mixpanelstarter
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactActivity
|
|
4
|
+
import com.facebook.react.ReactActivityDelegate
|
|
5
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
6
|
+
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
7
|
+
|
|
8
|
+
class MainActivity : ReactActivity() {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
|
12
|
+
* rendering of the component.
|
|
13
|
+
*/
|
|
14
|
+
override fun getMainComponentName(): String = "MixpanelStarter"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
|
|
18
|
+
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
|
|
19
|
+
*/
|
|
20
|
+
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
|
21
|
+
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
|
22
|
+
}
|
package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainApplication.kt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.mixpanelstarter
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import com.facebook.react.PackageList
|
|
5
|
+
import com.facebook.react.ReactApplication
|
|
6
|
+
import com.facebook.react.ReactHost
|
|
7
|
+
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
|
8
|
+
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
9
|
+
|
|
10
|
+
class MainApplication : Application(), ReactApplication {
|
|
11
|
+
|
|
12
|
+
override val reactHost: ReactHost by lazy {
|
|
13
|
+
getDefaultReactHost(
|
|
14
|
+
context = applicationContext,
|
|
15
|
+
packageList =
|
|
16
|
+
PackageList(this).packages.apply {
|
|
17
|
+
// Packages that cannot be autolinked yet can be added manually here, for example:
|
|
18
|
+
// add(MyReactNativePackage())
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun onCreate() {
|
|
24
|
+
super.onCreate()
|
|
25
|
+
loadReactNative(this)
|
|
26
|
+
}
|
|
27
|
+
}
|