Intro

A React Native Expo app to pick an image, upload it to ImgBB, and extract text using APILayer OCR in .

References


1. Prerequisites

  • Node.js (v18+ recommended)
  • npm or yarn
  • Expo CLI (npm install -g expo-cli)
  • A computer running Windows, macOS, or Linux

Accounts & Keys

  1. ImgBB – for image hosting

  2. APILayer Image-to-Text – for OCR


2. Create the Expo App

npx create-expo-app image-to-text-app
cd image-to-text-app

Install required dependencies:

npm install expo-image-picker expo-file-system react-native-safe-area-context

3. App Structure

image-to-text-app/
β”‚
β”œβ”€ app/
β”‚  └─ (tabs)/
β”‚      └─ ImageToTextScreen.tsx
β”œβ”€ package.json
└─ ...

4. ImageToTextScreen.tsx (Full Working Code)

// app/(tabs)/ImageToTextScreen.tsx
import * as ImagePicker from 'expo-image-picker';
import * as FileSystem from 'expo-file-system';
import { useState } from 'react';
import { Alert, Button, Image, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
 
export default function ImageToTextScreen() {
  const [imageUri, setImageUri] = useState<string | null>(null);
  const [extractedText, setExtractedText] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);
 
  const IMGBB_API_KEY = 'YOUR_IMGBB_KEY';
  const APILAYER_KEY = 'YOUR_APILAYER_KEY';
 
  const pickImage = async (fromCamera: boolean) => {
    const permission = fromCamera
      ? await ImagePicker.requestCameraPermissionsAsync()
      : await ImagePicker.requestMediaLibraryPermissionsAsync();
 
    if (!permission.granted) {
      Alert.alert('Permission required', 'Permission denied!');
      return;
    }
 
    const result = fromCamera
      ? await ImagePicker.launchCameraAsync({ allowsEditing: true, quality: 1 })
      : await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, quality: 1 });
 
    if (!result.canceled) {
      const uri = result.assets[0].uri;
      console.log('Picked image URI:', uri);
      setImageUri(uri);
      uploadToImgBB(uri);
    }
  };
 
  const uploadToImgBB = async (uri: string) => {
    try {
      setLoading(true);
      console.log('Reading file as base64...');
      const base64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
      console.log('Base64 length:', base64.length);
 
      const formData = new FormData();
      formData.append('image', base64);
 
      console.log('Uploading to ImgBB...');
      const imgbbRes = await fetch(`https://api.imgbb.com/1/upload?key=${IMGBB_API_KEY}`, {
        method: 'POST',
        body: formData,
      });
 
      const uploadData = await imgbbRes.json();
      console.log('ImgBB response:', uploadData);
 
      if (uploadData.error) throw new Error(uploadData.error.message);
      if (!uploadData.data?.url) throw new Error('Image upload failed');
 
      const imageUrl = uploadData.data.url;
      console.log('ImgBB URL:', imageUrl);
 
      console.log('Calling APILayer OCR...');
      const ocrRes = await fetch(`https://api.apilayer.com/image_to_text/url?url=${encodeURIComponent(imageUrl)}`, {
        method: 'GET',
        headers: { apikey: APILAYER_KEY },
      });
 
      const ocrData = await ocrRes.json();
      console.log('OCR response:', ocrData);
 
      setExtractedText(ocrData.all_text || 'No text found');
    } catch (err: any) {
      console.error('ImgBB upload or OCR error:', err);
      setExtractedText(`Error: ${err.message}`);
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.heading}>Image to Text OCR</Text>
 
      <View style={{ marginVertical: 8 }}>
        <Button title="Pick Image from Gallery" onPress={() => pickImage(false)} />
      </View>
      <View style={{ marginVertical: 8 }}>
        <Button title="Take Photo" onPress={() => pickImage(true)} />
      </View>
 
      {loading && <Text style={{ marginVertical: 8 }}>Processing...</Text>}
 
      {imageUri && <Image source={{ uri: imageUri }} style={styles.image} />}
 
      <ScrollView style={{ marginTop: 16, width: '90%' }}>
        <Text style={styles.label}>Extracted Text:</Text>
        <Text style={styles.text}>{extractedText}</Text>
      </ScrollView>
    </SafeAreaView>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', padding: 16, backgroundColor: '#fff' },
  heading: { fontSize: 24, fontWeight: 'bold', marginVertical: 16 },
  image: { width: 300, height: 300, resizeMode: 'contain', marginVertical: 16 },
  label: { fontSize: 18, fontWeight: 'bold' },
  text: { fontSize: 16, marginTop: 8 },
});

5. Notes

  1. Base64 Upload ensures proper image transmission to ImgBB.
  2. Logs at every stage show URI, base64 length, ImgBB response, and OCR output.
  3. Loading state indicates processing.
  4. Errors are logged for both ImgBB and OCR.

6. Run the App

npx expo start
  • Open in Expo Go on your phone.
  • Test gallery and camera uploads.
  • Observe logs in the Metro console.

7. Expected Flow

  1. Pick or capture an image β†’ log URI
  2. Convert to base64 β†’ log size
  3. Upload to ImgBB β†’ log response + public URL
  4. Call APILayer OCR β†’ log extracted text
  5. Display extracted text in the app