Intro
A React Native Expo app to pick an image, upload it to ImgBB, and extract text using APILayer OCR in .
References
Changes
Category Original Reference Refactored / Working Version Image Picker Separate functions for camera and gallery (pickImageGallery, pickImageCamera) Single pickImage(fromCamera: boolean) handles both sources Base64 Conversion & Upload Sent raw ImagePicker object to APILayer OCR Converts image URI to Base64 via FileSystem, uploads to ImgBB, gets URL OCR Workflow Directly calls APILayer OCR Two-step: upload to ImgBB first, then pass public URL to APILayer OCR Error Handling & Logging Minimal .catch() logging try/catch/finally at every step, logs all actions, shows user-friendly errors UI / UX Static text display Scrollable ScrollView for text, dynamic βProcessingβ¦β indicator, cleaner layout TypeScript Support JavaScript (App.js) TSX with typed state (string null, boolean) Routing / Structure Standalone App.js app/(tabs)/ImageToTextScreen.tsx in Expo Router, requires proper route setup Deprecation Fixes Used deprecated MediaTypeOptions and older FileSystem API Fixed to use legacy FileSystem API for Base64, adjusted media types
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
-
ImgBB β for image hosting
- Signup: https://imgbb.com/
- Get your API key.
-
APILayer Image-to-Text β for OCR
- Signup: https://apilayer.com/marketplace/image-to-text-api
- Get your API key.
2. Create the Expo App
npx create-expo-app image-to-text-app
cd image-to-text-appInstall required dependencies:
npm install expo-image-picker expo-file-system react-native-safe-area-context3. 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
- Base64 Upload ensures proper image transmission to ImgBB.
- Logs at every stage show URI, base64 length, ImgBB response, and OCR output.
- Loading state indicates processing.
- 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
- Pick or capture an image β log URI
- Convert to base64 β log size
- Upload to ImgBB β log response + public URL
- Call APILayer OCR β log extracted text
- Display extracted text in the app



