React Native Expo Event Management

1

Part 1: Introduction & Architecture

2

Part 2: Installation on Mac, Windows, Ubuntu

3

Part 3: Project Setup

4

Part 4: Admin Authentication

5

Part 5: Event Management

6

Part 6: QR Scanner for Attendance

7

Part 7: Polish & Deploy

This article is available in Indonesian

šŸ‡®šŸ‡© Baca dalam Bahasa Indonesia

January 21, 2026

šŸ‡¬šŸ‡§ English

React Native Expo Event Management Part 7: Polish & Deploy

Final touches including custom splash screen, error boundaries, network status indicator, EAS Build configuration, and Google Play Store deployment.

5 min read


šŸ“– Overview

In this final part, we'll:

  • Add UI polish (splash screen, empty states)
  • Implement error handling
  • Add network status indicator
  • Configure EAS Build
  • Deploy to Google Play Store

šŸŽØ Step 1: UI Polish

A. Empty State Component

File: components/EmptyState.tsx

import React from 'react';
import { View, Text } from 'react-native';
import { FileQuestion } from 'lucide-react-native';

interface EmptyStateProps {
  icon?: React.ReactNode;
  title: string;
  message?: string;
}

export default function EmptyState({ icon, title, message }: EmptyStateProps) {
  return (
    <View className="flex-1 items-center justify-center p-8">
      {icon || <FileQuestion size={64} color="#6b7280" />}
      <Text className="text-white text-xl font-bold mt-6 text-center">
        {title}
      </Text>
      {message && (
        <Text className="text-gray-400 text-center mt-2">
          {message}
        </Text>
      )}
    </View>
  );
}

B. Error Boundary Component

File: components/ErrorBoundary.tsx

import React, { Component, ReactNode } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { AlertTriangle, RefreshCw } from 'lucide-react-native';

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export default class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  handleReset = () => {
    this.setState({ hasError: false, error: undefined });
  };

  render() {
    if (this.state.hasError) {
      return (
        <View className="flex-1 bg-gray-900 items-center justify-center p-8">
          <AlertTriangle size={64} color="#ef4444" />
          <Text className="text-white text-xl font-bold mt-6 text-center">
            Something went wrong
          </Text>
          <Text className="text-gray-400 text-center mt-2 mb-6">
            {this.state.error?.message || 'An unexpected error occurred'}
          </Text>
          <TouchableOpacity
            className="bg-orange-500 px-6 py-3 rounded-xl flex-row items-center"
            onPress={this.handleReset}
          >
            <RefreshCw size={20} color="white" />
            <Text className="text-white font-semibold ml-2">Try Again</Text>
          </TouchableOpacity>
        </View>
      );
    }

    return this.props.children;
  }
}

C. Network Status Indicator

Install netinfo:

npx expo install @react-native-community/netinfo

File: components/NetworkStatus.tsx

import React, { useEffect, useState } from 'react';
import { View, Text, Animated } from 'react-native';
import NetInfo from '@react-native-community/netinfo';
import { WifiOff } from 'lucide-react-native';

export default function NetworkStatus() {
  const [isConnected, setIsConnected] = useState(true);
  const [slideAnim] = useState(new Animated.Value(-60));

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener((state) => {
      const connected = state.isConnected ?? true;
      setIsConnected(connected);
      
      Animated.timing(slideAnim, {
        toValue: connected ? -60 : 0,
        duration: 300,
        useNativeDriver: true,
      }).start();
    });

    return () => unsubscribe();
  }, []);

  return (
    <Animated.View
      style={{ transform: [{ translateY: slideAnim }] }}
      className="absolute top-0 left-0 right-0 bg-red-500 px-4 py-3 flex-row items-center justify-center z-50"
    >
      <WifiOff size={16} color="white" />
      <Text className="text-white ml-2 font-medium">No Internet Connection</Text>
    </Animated.View>
  );
}

šŸ›”ļø Step 2: Error Handling Utilities

File: utils/errorHandler.ts

import { Alert } from 'react-native';

export const handleApiError = (error: any, defaultMessage = 'An error occurred') => {
  console.error('API Error:', error);
  
  if (error.code === 'ECONNABORTED') {
    Alert.alert('Timeout', 'Request timed out. Please check your connection.');
    return;
  }
  
  if (error.response?.status === 401) {
    Alert.alert('Session Expired', 'Please login again.');
    return;
  }
  
  if (error.response?.status === 403) {
    Alert.alert('Access Denied', 'You do not have permission.');
    return;
  }
  
  if (error.response?.status >= 500) {
    Alert.alert('Server Error', 'Server is temporarily unavailable.');
    return;
  }
  
  Alert.alert('Error', error.response?.data?.error || defaultMessage);
};

export const withRetry = async <T>(
  fn: () => Promise<T>,
  retries = 3,
  delay = 1000
): Promise<T> => {
  try {
    return await fn();
  } catch (error) {
    if (retries > 0) {
      await new Promise((r) => setTimeout(r, delay));
      return withRetry(fn, retries - 1, delay * 2);
    }
    throw error;
  }
};

šŸ“¦ Step 3: Configure EAS Build

A. Install EAS CLI

npm install -g eas-cli
eas login

B. Initialize EAS

eas build:configure

C. Configure eas.json

{
  "cli": {
    "version": ">= 7.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "android": {
        "buildType": "apk"
      }
    },
    "production": {
      "android": {
        "buildType": "app-bundle"
      },
      "ios": {
        "resourceClass": "m-medium"
      }
    }
  },
  "submit": {
    "production": {}
  }
}

šŸš€ Step 4: Build for Android

Build APK (for testing)

eas build --platform android --profile preview

Build AAB (for Play Store)

eas build --platform android --profile production

šŸ“± Step 5: Deploy to Google Play Store

Prerequisites

  1. Google Play Developer account ($25 one-time fee)
  2. App icons and screenshots
  3. Privacy policy URL
  4. App description

Steps

  1. Create App in Play Console

  2. Upload AAB

    • Production → Create new release
    • Upload the AAB file from EAS
  3. Fill Store Listing

    • App name, description
    • Screenshots (phone, tablet)
    • Feature graphic (1024x500)
  4. Complete Content Rating

    • Answer questionnaire
    • Get rating
  5. Submit for Review

    • Review usually takes 1-3 days
    • First submission may take up to 7 days

āœ… Final Checklist

UI Polish

  • Empty state components
  • Error boundary
  • Network status indicator
  • Loading states

Error Handling

  • Centralized error handler
  • Retry logic
  • User-friendly messages

Build & Deploy

  • EAS Build configured
  • APK tested on device
  • AAB uploaded to Play Store
  • Store listing complete

šŸŽ‰ Congratulations!

You've completed the React Native Expo Event Management series! You now have:

  • āœ… A fully functional mobile app
  • āœ… Authentication with secure storage
  • āœ… Event management with real-time data
  • āœ… QR scanner for attendance
  • āœ… Production-ready build

What's Next?

  • Add push notifications
  • Implement offline mode
  • Add analytics
  • Create iOS version

← Part 6: QR Scanner | Back to Part 1 →


This series was created to support the Casual Meetup #15 at LampungDev. Thank you for following along!

Continue Reading

Previous article

← Previous Article

React Native Expo Event Management Part 6: QR Scanner Kehadiran