React Native SDK

The React Native SDK provides a complete suite of React hooks and platform-agnostic service adapters to build powerful food logging flows. It handles authentication, data synchronization, caching, and real-time image analysis.


Setup

Installation

The SDK requires React 19.0.0+ and React Native 0.83.0+. It provides zero-config support for Expo apps and modular adapter interfaces for bare React Native projects.

Expo & React Native

Import the core client and hooks from @macrofy/react-native. Whether you use Expo or bare React Native, the SDK handles it cleanly.

Install

bash
Terminal
npm install @macrofy/react-native expo-camera expo-file-system expo-secure-store

Initialization

import { Macrofy } from '@macrofy/react-native';

const client = new Macrofy();

Context

Provider Setup

To use the SDK hooks anywhere in your app, wrap your component tree in the MacrofyProvider.

The provider requires a connected client instance and the userId returned from a successful connect() handshake.

Once mounted, child components can freely use data hooks like useFoodSearch and useDiary.

App.tsx

import { useEffect, useState } from 'react';
import { MacrofyProvider, Macrofy } from '@macrofy/react-native';
import { FoodLogScreen } from './screens/FoodLogScreen';

const client = new Macrofy();

export default function App() {
  const [userId, setUserId] = useState<string | null>(null);

  useEffect(() => {
    // Authenticate using HMAC exchange
    client.connect({
      appId: "your_app_id",
      userId: "user_123",
      signature: "e3b0c44298fc1c149afbf4c8996fb924..."
    }).then(session => {
      setUserId(session.user.id);
    });
  }, []);

  if (!userId) return <LoadingSpinner />;

  return (
    <MacrofyProvider client={client} userId={userId}>
      <FoodLogScreen />
    </MacrofyProvider>
  );
}

HookuseFoodSearch

Debounced, cancelable text search across the Macrofy database.

As the user types, the hook waits 450ms (configurable) before firing the query. If the query changes before the network request completes, stale responses are silently discarded to prevent UI tearing.

Configuration

  • Name
    debounceMs
    Type
    number
    Description

    Time to wait after the last keystroke. Defaults to 450.

  • Name
    limit
    Type
    number
    Description

    Max results to return. Defaults to 20.

  • Name
    enabled
    Type
    boolean
    Description

    Set to false to pause queries (e.g. when a modal is closed).

FoodSearchUI.tsx

import { useState } from 'react';
import { TextInput, FlatList, Text, View } from 'react-native';
import { useFoodSearch } from '@macrofy/react-native';

export function FoodSearchUI() {
  const [query, setQuery] = useState("");
  
  const { results, loading, error } = useFoodSearch(query, {
    debounceMs: 300,
    limit: 10
  });

  return (
    <View>
      <TextInput
        placeholder="Search for food..."
        value={query}
        onChangeText={setQuery}
      />
      
      {loading && <Text>Searching...</Text>}
      {error && <Text className="text-red-500">{error.message}</Text>}

      <FlatList
        data={results}
        keyExtractor={item => item.id}
        renderItem={({ item }) => (
          <View>
            <Text>{item.icon} {item.name}</Text>
            <Text>{item.calories} kcal</Text>
          </View>
        )}
      />
    </View>
  );
}

HookuseImageScanner

Image Scanner

The primary hook for AI-powered meal tracking. It takes a local image URI from the device, creates a processing job, uploads the raw bytes directly to GCS via the native adapter, and opens a Server-Sent Events (SSE) stream to wait for the analysis result.

The returned FoodData object contains the exact macros, serving size, and identified food name.

CameraFlow.tsx

import { useState } from 'react';
import { Button, Text, View } from 'react-native';
import { useImageScanner } from '@macrofy/react-native';

export function CameraFlow({ photoUri }) {
  const { scan, loading, error } = useImageScanner();
  const [result, setResult] = useState(null);

  const handleScan = async () => {
    try {
      const food = await scan(photoUri, "photo");
      setResult(food);
    } catch (e) {
      console.error("Scan failed", e);
    }
  };

  return (
    <View>
      <Button 
        title={loading ? "Analyzing Magic..." : "Scan Meal"} 
        onPress={handleScan} 
        disabled={loading} 
      />
      
      {result && (
        <View>
          <Text>Identified: {result.name}</Text>
          <Text>Calories: {result.calories}</Text>
          <Text>Protein: {result.protein}g</Text>
        </View>
      )}
    </View>
  );
}

HookuseBarcodeScanner

Barcode Scanner

Resolves a scanned UPC/EAN code into a structured FoodData object.

Pass the raw string captured by your device's camera library (e.g. expo-camera). The Macrofy backend automatically tests padded and unpadded format variants to find the right item.

BarcodeScan.tsx

import { useBarcodeScanner } from '@macrofy/react-native';

export function BarcodeScanner() {
  const { scan, loading } = useBarcodeScanner();

  const onBarcodeDetected = async (code: string) => {
    try {
      const food = await scan(code);
      alert(`Found: ${food.name} (${food.calories} kcal)`);
    } catch (error) {
      alert('Barcode not found in database.');
    }
  };

  return <YourCameraView onCode={onBarcodeDetected} />;
}

HookuseDiary

Diary & Server Sync

Manages user nutrition entries for a specific day.

Macrofy handles diary persistence entirely server-side. There is no local SQLite or cross-device sync conflicts to manage.

The hook auto-fetches entries whenever the date parameter changes, and any addEntry or deleteEntry operations instantly re-sync the remote data.

DailyLog.tsx

import { useDiary } from '@macrofy/react-native';

export function DailyLog() {
  // Defaults to today if no date is provided
  const { entries, loading, addEntry, deleteEntry } = useDiary();

  const trackApple = async () => {
    await addEntry({
      date: "2026-03-12",
      mealType: "snacks",
      foodName: "Apple",
      calories: 95,
      protein: 0.5,
      carbs: 25,
      fat: 0.3,
      addedMethod: "manual"
    });
  };

  return (
    <View>
      {loading && <Spinner />}
      {entries.map(entry => (
        <MealRow 
          key={entry.id} 
          item={entry} 
          onDelete={() => deleteEntry(entry.id)} 
        />
      ))}
      <Button title="Quick Add Apple" onPress={trackApple} />
    </View>
  );
}

LogicuseDailyTotals / useMacroData

Daily Totals & Macros

Combine useDailyTotals and useMacroData to easily construct progress rings and macro bars for your dashboard.

  • useDailyTotals queries the aggregated sum of all diary entries for a given date.
  • useMacroData combines those totals with the user's computed BMR goals (from useProfile) to yield { current, goal } tuples for protein, carbs, and fat.

DashboardHeader.tsx

import { useDailyTotals, useProfile, useMacroData } from '@macrofy/react-native';

export function DashboardHeader({ dateStr }) {
  const { totals } = useDailyTotals(dateStr);
  const { goals } = useProfile();
  
  // Pure computation combining both state trees
  const macros = useMacroData(totals, goals);

  return (
    <View>
      <Text>
        Calories: {totals.calories} / {goals?.calories || 0}
      </Text>
      
      {/* Use tuple data for UI progress bars */}
      <ProgressBar 
        label="Protein" 
        current={macros.protein.current} 
        max={macros.protein.goal} 
      />
      <ProgressBar 
        label="Carbs" 
        current={macros.carbs.current} 
        max={macros.carbs.goal} 
      />
    </View>
  );
}

Was this page helpful?