RoxyAPI

Menu

How to Build a Name Numerology Analyzer (React Native + API Tutorial)

16 min read
By Marcus Thompson
NumerologyName AnalysisReact NativeExpression NumberSoul Urge

Build a complete name analysis tool with Expression, Soul Urge, and Personality numbers. Real-time calculations, beautiful UI, and instant interpretations.

How to Build a Name Numerology Analyzer (React Native + API Tutorial)

Your name carries powerful vibrations. In numerology, every letter corresponds to a number, and these numbers reveal your talents, desires, and how the world perceives you. This tutorial will teach you how to build a complete name numerology analyzer that calculates three core numbers from a person's full birth name.

We will build a React Native app that uses the RoxyAPI Numerology API to analyze names and display instant interpretations. By the end, you will have a production-ready feature that can power self-discovery apps, personal development platforms, or spiritual guidance tools.

The Three Core Name Numbers

Before coding, let us understand what each number reveals:

1. Expression Number (Destiny Number)

Calculated from: All letters in full birth name
Reveals: Natural talents, abilities, life purpose, what you came here to do

Example: "John William Smith"
J(1) + O(6) + H(8) + N(5) + W(5) + I(9) + L(3) + L(3) + I(9) + A(1) + M(4) + S(1) + M(4) + I(9) + T(2) + H(8) = 88
8 + 8 = 16
1 + 6 = 7 Expression

A 7 Expression indicates natural gifts for analysis, research, spirituality, and deep thinking. Career paths: scientist, philosopher, spiritual teacher, analyst.

2. Soul Urge Number (Heart Desire Number)

Calculated from: Only vowels in full birth name
Reveals: Inner motivations, emotional needs, what your soul truly desires

Example: "John William Smith"
Vowels: O(6) + I(9) + I(9) + A(1) + I(9) = 34
3 + 4 = 7 Soul Urge

A 7 Soul Urge craves solitude, spiritual truth, deep understanding. This person needs time alone to recharge and connect with inner wisdom.

3. Personality Number

Calculated from: Only consonants in full birth name
Reveals: How others perceive you, first impressions, outer personality

Example: "John William Smith"
Consonants: J(1) + H(8) + N(5) + W(5) + L(3) + L(3) + M(4) + S(1) + M(4) + T(2) + H(8) = 44
4 + 4 = 8 Personality

An 8 Personality projects authority, competence, ambition. Others see this person as powerful, successful, and capable of leadership.

Master Numbers (11, 22, 33)

These special numbers are NOT reduced further in name calculations:

  • 11: Spiritual illumination, intuition, inspiration
  • 22: Master builder, practical visionary, large-scale achievements
  • 33: Master teacher, selfless service, spiritual healing

When you encounter 11, 22, or 33 during calculation, keep them as-is.

What We Will Build

This tutorial covers:

  1. Name Input Component - Clean UI with real-time validation
  2. API Integration - Three endpoints (Expression, Soul Urge, Personality)
  3. Results Display - Beautiful cards showing all three numbers
  4. Interpretation Details - Expandable sections with career, relationships, spirituality
  5. Share Feature - Let users share their analysis results
  6. History Tracking - Save analyzed names for later reference

Prerequisites

  • React Native project (Expo or bare workflow)
  • Node.js 18+ or Bun
  • RoxyAPI account (sign up here)
  • Basic TypeScript knowledge

Step 1: Setting Up API Clients

Create reusable API clients for name analysis:

// lib/numerology-api.ts
const NUMEROLOGY_API = 'https://roxyapi.com/api/v2/numerology';
const API_KEY = process.env.EXPO_PUBLIC_ROXYAPI_KEY!;

export interface NameNumber {
  number: number; // 1-9, 11, 22, or 33
  calculation: string; // "J(1) + O(6) + H(8)... = 88 → 8+8 = 16 → 1+6 = 7"
  type: 'single' | 'master'; // Master numbers: 11, 22, 33
  hasKarmicDebt: boolean;
  karmicDebtNumber?: number; // 13, 14, 16, or 19
  meaning: {
    title: string; // "The Seeker" for 7
    keywords: string[]; // ["analytical", "spiritual", "wise"]
    description: string; // 300-500 word interpretation
    strengths: string[];
    challenges: string[];
    career: string;
    relationships: string;
    spirituality: string;
  };
}

export async function calculateExpression(fullName: string): Promise<NameNumber> {
  const response = await fetch(`${NUMEROLOGY_API}/expression`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ fullName }),
  });

  if (!response.ok) {
    throw new Error(`Expression API error: ${response.statusText}`);
  }

  return response.json();
}

export async function calculateSoulUrge(fullName: string): Promise<NameNumber> {
  const response = await fetch(`${NUMEROLOGY_API}/soul-urge`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ fullName }),
  });

  if (!response.ok) {
    throw new Error(`Soul Urge API error: ${response.statusText}`);
  }

  return response.json();
}

export async function calculatePersonality(fullName: string): Promise<NameNumber> {
  const response = await fetch(`${NUMEROLOGY_API}/personality`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ fullName }),
  });

  if (!response.ok) {
    throw new Error(`Personality API error: ${response.statusText}`);
  }

  return response.json();
}

export interface NameAnalysis {
  fullName: string;
  expression: NameNumber;
  soulUrge: NameNumber;
  personality: NameNumber;
  analyzedAt: string;
}

export async function analyzeFullName(fullName: string): Promise<NameAnalysis> {
  // Call all three endpoints in parallel
  const [expression, soulUrge, personality] = await Promise.all([
    calculateExpression(fullName),
    calculateSoulUrge(fullName),
    calculatePersonality(fullName),
  ]);

  return {
    fullName,
    expression,
    soulUrge,
    personality,
    analyzedAt: new Date().toISOString(),
  };
}

Step 2: Name Input Component with Validation

Build a clean input form with real-time validation:

// components/NameInput.tsx
import React, { useState } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  ActivityIndicator,
} from 'react-native';

interface NameInputProps {
  onAnalyze: (fullName: string) => Promise<void>;
}

export function NameInput({ onAnalyze }: NameInputProps) {
  const [fullName, setFullName] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const validate = (name: string): string | null => {
    if (name.trim().length === 0) {
      return 'Please enter a name';
    }

    if (name.trim().length < 3) {
      return 'Name must be at least 3 characters';
    }

    // Check for at least first and last name
    const parts = name.trim().split(/\s+/);
    if (parts.length < 2) {
      return 'Please enter at least first and last name';
    }

    // Only allow letters and spaces
    if (!/^[a-zA-Z\s]+$/.test(name)) {
      return 'Name can only contain letters and spaces';
    }

    return null;
  };

  const handleAnalyze = async () => {
    const validationError = validate(fullName);
    if (validationError) {
      setError(validationError);
      return;
    }

    setError(null);
    setLoading(true);

    try {
      await onAnalyze(fullName);
    } catch (err) {
      setError('Failed to analyze name. Please try again.');
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Enter Your Full Birth Name</Text>
      <Text style={styles.subtitle}>
        Use the exact name on your birth certificate for accurate results
      </Text>

      <TextInput
        style={[styles.input, error && styles.inputError]}
        placeholder="John William Smith"
        placeholderTextColor="#9CA3AF"
        value={fullName}
        onChangeText={(text) => {
          setFullName(text);
          setError(null);
        }}
        autoCapitalize="words"
        autoCorrect={false}
        editable={!loading}
      />

      {error && <Text style={styles.errorText}>{error}</Text>}

      <TouchableOpacity
        style={[styles.button, loading && styles.buttonDisabled]}
        onPress={handleAnalyze}
        disabled={loading}
      >
        {loading ? (
          <ActivityIndicator color="#FFFFFF" />
        ) : (
          <Text style={styles.buttonText}>Analyze Name</Text>
        )}
      </TouchableOpacity>

      <View style={styles.exampleContainer}>
        <Text style={styles.exampleTitle}>Examples:</Text>
        <TouchableOpacity onPress={() => setFullName('Albert Einstein')}>
          <Text style={styles.exampleName}>Albert Einstein</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => setFullName('Marie Curie')}>
          <Text style={styles.exampleName}>Marie Curie</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => setFullName('Mahatma Gandhi')}>
          <Text style={styles.exampleName}>Mahatma Gandhi</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    backgroundColor: '#FFFFFF',
    borderRadius: 16,
    marginHorizontal: 16,
    marginBottom: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 4,
  },
  title: {
    fontSize: 22,
    fontWeight: 'bold',
    color: '#111827',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 14,
    color: '#6B7280',
    marginBottom: 20,
    lineHeight: 20,
  },
  input: {
    borderWidth: 2,
    borderColor: '#E5E7EB',
    borderRadius: 12,
    padding: 16,
    fontSize: 16,
    color: '#111827',
    backgroundColor: '#F9FAFB',
    marginBottom: 12,
  },
  inputError: {
    borderColor: '#EF4444',
  },
  errorText: {
    color: '#EF4444',
    fontSize: 14,
    marginBottom: 12,
  },
  button: {
    backgroundColor: '#6B46C1',
    borderRadius: 12,
    padding: 16,
    alignItems: 'center',
    marginBottom: 16,
  },
  buttonDisabled: {
    backgroundColor: '#9CA3AF',
  },
  buttonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  exampleContainer: {
    borderTopWidth: 1,
    borderTopColor: '#E5E7EB',
    paddingTop: 16,
  },
  exampleTitle: {
    fontSize: 12,
    fontWeight: '600',
    color: '#6B7280',
    marginBottom: 8,
    textTransform: 'uppercase',
    letterSpacing: 1,
  },
  exampleName: {
    fontSize: 14,
    color: '#6B46C1',
    paddingVertical: 4,
  },
});

Step 3: Number Card Component

Display each number with interpretation:

// components/NumberCard.tsx
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { ChevronDown, ChevronUp } from 'lucide-react-native';
import type { NameNumber } from '../lib/numerology-api';

interface NumberCardProps {
  label: string;
  subtitle: string;
  data: NameNumber;
  color: string;
  icon: string;
}

export function NumberCard({ label, subtitle, data, color, icon }: NumberCardProps) {
  const [expanded, setExpanded] = useState(false);

  return (
    <View style={[styles.card, { borderLeftColor: color }]}>
      <View style={styles.header}>
        <View style={styles.iconContainer}>
          <Text style={styles.icon}>{icon}</Text>
        </View>
        <View style={styles.titleContainer}>
          <Text style={styles.label}>{label}</Text>
          <Text style={styles.subtitle}>{subtitle}</Text>
        </View>
        <View style={[styles.numberBadge, { backgroundColor: color }]}>
          <Text style={styles.numberText}>{data.number}</Text>
        </View>
      </View>

      <Text style={styles.title}>{data.meaning.title}</Text>

      <View style={styles.keywordsContainer}>
        {data.meaning.keywords.slice(0, 4).map((keyword, index) => (
          <View key={index} style={[styles.keyword, { borderColor: color }]}>
            <Text style={[styles.keywordText, { color }]}>{keyword}</Text>
          </View>
        ))}
      </View>

      {data.type === 'master' && (
        <View style={styles.masterBadge}>
          <Text style={styles.masterBadgeText}>✨ Master Number</Text>
        </View>
      )}

      {data.hasKarmicDebt && (
        <View style={styles.karmicBadge}>
          <Text style={styles.karmicBadgeText}>
            ⚠️ Karmic Debt {data.karmicDebtNumber}
          </Text>
        </View>
      )}

      <TouchableOpacity
        style={styles.expandButton}
        onPress={() => setExpanded(!expanded)}
      >
        <Text style={[styles.expandText, { color }]}>
          {expanded ? 'Show Less' : 'Read Full Interpretation'}
        </Text>
        {expanded ? (
          <ChevronUp size={20} color={color} />
        ) : (
          <ChevronDown size={20} color={color} />
        )}
      </TouchableOpacity>

      {expanded && (
        <View style={styles.detailsContainer}>
          <Text style={styles.description}>{data.meaning.description}</Text>

          <View style={styles.section}>
            <Text style={styles.sectionTitle}>💪 Strengths</Text>
            {data.meaning.strengths.map((strength, index) => (
              <Text key={index} style={styles.listItem}>
                • {strength}
              </Text>
            ))}
          </View>

          <View style={styles.section}>
            <Text style={styles.sectionTitle}>⚠️ Challenges</Text>
            {data.meaning.challenges.map((challenge, index) => (
              <Text key={index} style={styles.listItem}>
                • {challenge}
              </Text>
            ))}
          </View>

          <View style={styles.section}>
            <Text style={styles.sectionTitle}>💼 Career Paths</Text>
            <Text style={styles.paragraph}>{data.meaning.career}</Text>
          </View>

          <View style={styles.section}>
            <Text style={styles.sectionTitle}>❤️ Relationships</Text>
            <Text style={styles.paragraph}>{data.meaning.relationships}</Text>
          </View>

          <View style={styles.section}>
            <Text style={styles.sectionTitle}>🙏 Spiritual Path</Text>
            <Text style={styles.paragraph}>{data.meaning.spirituality}</Text>
          </View>

          <View style={styles.calculationBox}>
            <Text style={styles.calculationTitle}>📊 Calculation</Text>
            <Text style={styles.calculationText}>{data.calculation}</Text>
          </View>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#FFFFFF',
    borderRadius: 16,
    padding: 20,
    marginHorizontal: 16,
    marginBottom: 16,
    borderLeftWidth: 4,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 4,
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 16,
  },
  iconContainer: {
    width: 48,
    height: 48,
    borderRadius: 24,
    backgroundColor: '#F9FAFB',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  icon: {
    fontSize: 24,
  },
  titleContainer: {
    flex: 1,
  },
  label: {
    fontSize: 16,
    fontWeight: '600',
    color: '#111827',
    marginBottom: 2,
  },
  subtitle: {
    fontSize: 12,
    color: '#6B7280',
  },
  numberBadge: {
    width: 48,
    height: 48,
    borderRadius: 24,
    alignItems: 'center',
    justifyContent: 'center',
  },
  numberText: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#FFFFFF',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#111827',
    marginBottom: 12,
  },
  keywordsContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    marginBottom: 12,
  },
  keyword: {
    borderWidth: 1,
    borderRadius: 12,
    paddingHorizontal: 12,
    paddingVertical: 6,
    marginRight: 8,
    marginBottom: 8,
  },
  keywordText: {
    fontSize: 12,
    fontWeight: '600',
  },
  masterBadge: {
    backgroundColor: '#FEF3C7',
    borderRadius: 8,
    padding: 10,
    marginBottom: 12,
  },
  masterBadgeText: {
    fontSize: 13,
    fontWeight: '600',
    color: '#92400E',
  },
  karmicBadge: {
    backgroundColor: '#FEE2E2',
    borderRadius: 8,
    padding: 10,
    marginBottom: 12,
  },
  karmicBadgeText: {
    fontSize: 13,
    fontWeight: '600',
    color: '#991B1B',
  },
  expandButton: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    paddingVertical: 12,
  },
  expandText: {
    fontSize: 14,
    fontWeight: '600',
    marginRight: 6,
  },
  detailsContainer: {
    marginTop: 16,
    borderTopWidth: 1,
    borderTopColor: '#E5E7EB',
    paddingTop: 16,
  },
  description: {
    fontSize: 14,
    color: '#4B5563',
    lineHeight: 22,
    marginBottom: 20,
  },
  section: {
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 15,
    fontWeight: '600',
    color: '#111827',
    marginBottom: 8,
  },
  listItem: {
    fontSize: 14,
    color: '#4B5563',
    lineHeight: 22,
    marginLeft: 8,
  },
  paragraph: {
    fontSize: 14,
    color: '#4B5563',
    lineHeight: 22,
  },
  calculationBox: {
    backgroundColor: '#F9FAFB',
    borderRadius: 8,
    padding: 12,
    marginTop: 8,
  },
  calculationTitle: {
    fontSize: 13,
    fontWeight: '600',
    color: '#6B7280',
    marginBottom: 6,
  },
  calculationText: {
    fontSize: 12,
    color: '#4B5563',
    fontFamily: 'monospace',
  },
});

Step 4: Full Analysis Screen

Combine everything into a complete screen:

// screens/NameAnalyzerScreen.tsx
import React, { useState } from 'react';
import { ScrollView, View, Text, StyleSheet, Share } from 'react-native';
import { NameInput } from '../components/NameInput';
import { NumberCard } from '../components/NumberCard';
import { analyzeFullName, type NameAnalysis } from '../lib/numerology-api';

export function NameAnalyzerScreen() {
  const [analysis, setAnalysis] = useState<NameAnalysis | null>(null);

  const handleAnalyze = async (fullName: string) => {
    const result = await analyzeFullName(fullName);
    setAnalysis(result);
  };

  const handleShare = async () => {
    if (!analysis) return;

    const message = `
🔮 Numerology Analysis for ${analysis.fullName}

${analysis.expression.number} Expression (${analysis.expression.meaning.title})
Natural talents and life purpose

${analysis.soulUrge.number} Soul Urge (${analysis.soulUrge.meaning.title})
Inner desires and motivations

${analysis.personality.number} Personality (${analysis.personality.meaning.title})
How others perceive you

Calculate your own: [Your App Link]
    `.trim();

    try {
      await Share.share({ message });
    } catch (error) {
      console.error('Failed to share:', error);
    }
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>Name Numerology Analyzer</Text>
        <Text style={styles.headerSubtitle}>
          Discover your talents, desires, and public persona through your birth name
        </Text>
      </View>

      <NameInput onAnalyze={handleAnalyze} />

      {analysis && (
        <View style={styles.resultsContainer}>
          <View style={styles.resultsHeader}>
            <Text style={styles.resultsTitle}>Analysis for {analysis.fullName}</Text>
            <TouchableOpacity style={styles.shareButton} onPress={handleShare}>
              <Text style={styles.shareButtonText}>Share Results</Text>
            </TouchableOpacity>
          </View>

          <NumberCard
            label="Expression Number"
            subtitle="Natural talents and life purpose"
            data={analysis.expression}
            color="#6B46C1"
            icon="🎯"
          />

          <NumberCard
            label="Soul Urge Number"
            subtitle="Inner desires and motivations"
            data={analysis.soulUrge}
            color="#EC4899"
            icon="💖"
          />

          <NumberCard
            label="Personality Number"
            subtitle="How others perceive you"
            data={analysis.personality}
            color="#3B82F6"
            icon="👤"
          />

          <View style={styles.infoBox}>
            <Text style={styles.infoTitle}>Understanding Your Numbers</Text>
            <Text style={styles.infoText}>
              Your <Text style={styles.bold}>Expression number</Text> shows what you came here to do.
              Your <Text style={styles.bold}>Soul Urge</Text> reveals what your heart truly desires.
              Your <Text style={styles.bold}>Personality</Text> reflects the mask you show the world.
            </Text>
            <Text style={styles.infoText}>
              Together, these three numbers paint a complete picture of who you are at your core.
            </Text>
          </View>
        </View>
      )}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F9FAFB',
  },
  header: {
    padding: 20,
    paddingTop: 60,
    paddingBottom: 24,
  },
  headerTitle: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#111827',
    marginBottom: 8,
  },
  headerSubtitle: {
    fontSize: 16,
    color: '#6B7280',
    lineHeight: 24,
  },
  resultsContainer: {
    paddingVertical: 8,
  },
  resultsHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 16,
  },
  resultsTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#111827',
    flex: 1,
  },
  shareButton: {
    backgroundColor: '#6B46C1',
    borderRadius: 8,
    paddingHorizontal: 16,
    paddingVertical: 8,
  },
  shareButtonText: {
    color: '#FFFFFF',
    fontSize: 14,
    fontWeight: '600',
  },
  infoBox: {
    backgroundColor: '#EEF2FF',
    borderRadius: 16,
    padding: 20,
    marginHorizontal: 16,
    marginTop: 8,
    marginBottom: 20,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#3730A3',
    marginBottom: 10,
  },
  infoText: {
    fontSize: 14,
    color: '#4338CA',
    lineHeight: 22,
    marginBottom: 8,
  },
  bold: {
    fontWeight: '600',
  },
});

Step 5: Caching and History

Save analyses to local storage:

// lib/storage.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import type { NameAnalysis } from './numerology-api';

const HISTORY_KEY = '@numerology_history';

export async function saveAnalysis(analysis: NameAnalysis): Promise<void> {
  const history = await getHistory();
  const updated = [analysis, ...history.filter((h) => h.fullName !== analysis.fullName)];
  await AsyncStorage.setItem(HISTORY_KEY, JSON.stringify(updated.slice(0, 20))); // Keep last 20
}

export async function getHistory(): Promise<NameAnalysis[]> {
  const data = await AsyncStorage.getItem(HISTORY_KEY);
  return data ? JSON.parse(data) : [];
}

export async function clearHistory(): Promise<void> {
  await AsyncStorage.removeItem(HISTORY_KEY);
}

Add history screen:

// screens/HistoryScreen.tsx
import React, { useEffect, useState } from 'react';
import { FlatList, View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { getHistory, type NameAnalysis } from '../lib/storage';

export function HistoryScreen({ navigation }: any) {
  const [history, setHistory] = useState<NameAnalysis[]>([]);

  useEffect(() => {
    loadHistory();
  }, []);

  const loadHistory = async () => {
    const data = await getHistory();
    setHistory(data);
  };

  const renderItem = ({ item }: { item: NameAnalysis }) => (
    <TouchableOpacity
      style={styles.card}
      onPress={() => navigation.navigate('Analysis', { analysis: item })}
    >
      <Text style={styles.name}>{item.fullName}</Text>
      <View style={styles.numbersRow}>
        <View style={styles.numberBadge}>
          <Text style={styles.badgeText}>{item.expression.number} Expression</Text>
        </View>
        <View style={styles.numberBadge}>
          <Text style={styles.badgeText}>{item.soulUrge.number} Soul Urge</Text>
        </View>
        <View style={styles.numberBadge}>
          <Text style={styles.badgeText}>{item.personality.number} Personality</Text>
        </View>
      </View>
      <Text style={styles.date}>
        {new Date(item.analyzedAt).toLocaleDateString()}
      </Text>
    </TouchableOpacity>
  );

  if (history.length === 0) {
    return (
      <View style={styles.emptyContainer}>
        <Text style={styles.emptyText}>No analyses yet</Text>
        <Text style={styles.emptySubtext}>
          Analyze a name to see it here
        </Text>
      </View>
    );
  }

  return (
    <FlatList
      data={history}
      renderItem={renderItem}
      keyExtractor={(item) => item.fullName}
      contentContainerStyle={styles.list}
    />
  );
}

const styles = StyleSheet.create({
  list: {
    padding: 16,
  },
  card: {
    backgroundColor: '#FFFFFF',
    borderRadius: 16,
    padding: 16,
    marginBottom: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 4,
  },
  name: {
    fontSize: 18,
    fontWeight: '600',
    color: '#111827',
    marginBottom: 12,
  },
  numbersRow: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    marginBottom: 8,
  },
  numberBadge: {
    backgroundColor: '#EEF2FF',
    borderRadius: 8,
    paddingHorizontal: 10,
    paddingVertical: 6,
    marginRight: 8,
    marginBottom: 8,
  },
  badgeText: {
    fontSize: 12,
    fontWeight: '600',
    color: '#4338CA',
  },
  date: {
    fontSize: 12,
    color: '#6B7280',
  },
  emptyContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 40,
  },
  emptyText: {
    fontSize: 20,
    fontWeight: '600',
    color: '#6B7280',
    marginBottom: 8,
  },
  emptySubtext: {
    fontSize: 14,
    color: '#9CA3AF',
  },
});

Production Enhancements

1. Error Handling

// lib/error-handler.ts
export class NumerologyError extends Error {
  constructor(
    message: string,
    public code: 'NETWORK_ERROR' | 'INVALID_NAME' | 'API_ERROR' | 'RATE_LIMIT'
  ) {
    super(message);
  }
}

export function handleNumerologyError(error: unknown): NumerologyError {
  if (error instanceof NumerologyError) {
    return error;
  }

  if (error instanceof TypeError && error.message.includes('fetch')) {
    return new NumerologyError(
      'Network connection failed. Please check your internet.',
      'NETWORK_ERROR'
    );
  }

  return new NumerologyError('An unexpected error occurred', 'API_ERROR');
}

2. Offline Support

// Cache API responses
import NetInfo from '@react-native-community/netinfo';

export async function analyzeFullNameCached(fullName: string): Promise<NameAnalysis> {
  const cacheKey = `analysis_${fullName.toLowerCase().replace(/\s+/g, '_')}`;
  
  // Check cache first
  const cached = await AsyncStorage.getItem(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // Check network connectivity
  const netInfo = await NetInfo.fetch();
  if (!netInfo.isConnected) {
    throw new NumerologyError(
      'No internet connection. Please try again when online.',
      'NETWORK_ERROR'
    );
  }
  
  // Fetch from API
  const analysis = await analyzeFullName(fullName);
  
  // Cache for 30 days
  await AsyncStorage.setItem(cacheKey, JSON.stringify(analysis));
  
  return analysis;
}

3. Analytics Tracking

// Track user behavior
import * as Analytics from 'expo-firebase-analytics';

export async function trackNameAnalysis(analysis: NameAnalysis) {
  await Analytics.logEvent('name_analyzed', {
    expression_number: analysis.expression.number,
    soul_urge_number: analysis.soulUrge.number,
    personality_number: analysis.personality.number,
    is_master_expression: analysis.expression.type === 'master',
    is_master_soul: analysis.soulUrge.type === 'master',
    is_master_personality: analysis.personality.type === 'master',
    has_karmic_debt: analysis.expression.hasKarmicDebt ||
      analysis.soulUrge.hasKarmicDebt ||
      analysis.personality.hasKarmicDebt,
  });
}

Production Checklist

  • API key stored securely (environment variables, not hardcoded)
  • Input validation prevents API abuse
  • Response caching reduces API costs
  • Offline mode with graceful error messages
  • Analytics tracking for user behavior
  • Share functionality tested on iOS and Android
  • History limited to last 20 analyses
  • Loading states and skeleton screens
  • Error boundaries for crash prevention
  • Rate limiting awareness (show user-friendly messages)

Pricing Considerations

RoxyAPI charges per request. Optimize costs with:

  1. Cache aggressively: Names do not change, cache forever
  2. Parallel requests: Call all 3 endpoints simultaneously (3 API calls per analysis)
  3. Batch processing: If analyzing multiple names, use Promise.all()
  4. Free tier: Start with free tier (50 requests/month) for development

Example calculation:

  • 100 name analyses per day = 300 API calls/day = 9,000 calls/month
  • Cost at Starter plan ($19/month): 10,000 requests included = $0.01 cost

Next Steps

Extend your name analyzer with:

  • Compatibility Matching: Compare two people and numbers for relationship insights
  • Baby Name Search: Filter names by desired Expression or Soul Urge number
  • Business Name Analysis: Apply numerology to company/brand names
  • PDF Reports: Generate downloadable PDFs with full analysis
  • Numerology Chart: Combine with Life Path, Birthday, Maturity numbers

Explore complementary APIs:

Conclusion

You now have a production-ready name numerology analyzer that calculates Expression, Soul Urge, and Personality numbers with instant interpretations. This feature provides deep self-discovery value that keeps users engaged and returning to your app.

The key is combining accurate calculations from RoxyAPI with beautiful, intuitive UI that makes complex numerology accessible to everyone.

Start building today: Sign up for RoxyAPI and implement name analysis in your app. Full documentation at roxyapi.com/docs.