Skip to content

Add Food Search to a React Native App With a Nutrition API

Published June 15, 2026

Users abandon calorie trackers when logging a meal takes more than ten seconds. Food search is the bottleneck. This guide shows how to add food search to a React Native app using a nutrition API, with UX patterns that feel instant even on slow networks.

Why Search UX Makes or Breaks Your App

Bad patternUser reaction
Search on every keystrokeLaggy keyboard, wasted API calls
No loading indicator"Is it broken?"
Generic error toastUninstall
No recent foodsRe-searching "oatmeal" daily

Good search feels like Apple Health or MyFitnessPal: type two letters, pick a result, done.

Architecture Overview

User types → debounce 300ms → your backend → Calorie API /search/foods or /search/suggest
                     ↓
              FlatList results → tap → food detail → log entry

Never call api.calorieapi.com with a secret key from JavaScript in production. Route through Next.js or Supabase.

Use Suggest for Autocomplete, Search for Full Results

Calorie API exposes:

EndpointBest for
/search/suggest?q=chiTypeahead dropdown (fast, short names)
/search/foods?q=chicken+breast&limit=20Full result list with macros

For a dropdown under the TextInput, prefer suggest. When the user submits or taps "See all", call search.

export async function suggestFoods(q: string) {
  const res = await fetch(
    `NULL/api/foods/suggest?q=${encodeURIComponent(q)}`
  );
  return res.json();
}

Complete Search Hook

import { useEffect, useRef, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

const RECENT_KEY = 'recent_foods_v1';

export function useFoodSearch() {
  const [query, setQuery] = useState('');
  const [suggestions, setSuggestions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [recents, setRecents] = useState([]);
  const abortRef = useRef<AbortController | null>(null);

  useEffect(() => {
    AsyncStorage.getItem(RECENT_KEY).then((raw) => {
      if (raw) setRecents(JSON.parse(raw));
    });
  }, []);

  useEffect(() => {
    const trimmed = query.trim();
    if (trimmed.length < 2) {
      setSuggestions([]);
      return;
    }
    const timer = setTimeout(async () => {
      abortRef.current?.abort();
      abortRef.current = new AbortController();
      setLoading(true);
      try {
        const res = await fetch(
          `NULL/api/foods/suggest?q=${encodeURIComponent(trimmed)}`,
          { signal: abortRef.current.signal }
        );
        const data = await res.json();
        setSuggestions(data.results ?? data.suggestions ?? []);
      } catch (e) {
        if ((e as Error).name !== 'AbortError') setSuggestions([]);
      } finally {
        setLoading(false);
      }
    }, 300);
    return () => clearTimeout(timer);
  }, [query]);

  async function selectFood(food) {
    const next = [food, ...recents.filter((r) => r.id !== food.id)].slice(0, 8);
    setRecents(next);
    await AsyncStorage.setItem(RECENT_KEY, JSON.stringify(next));
    return food;
  }

  return { query, setQuery, suggestions, loading, recents, selectFood };
}

AbortController cancels stale requests when the user types quickly. AsyncStorage recents cut API calls for repeat logs.

UI Components That Convert

Empty state (query too short)

Show recents and popular foods instead of a blank screen.

Result row design

Display: food name, brand (if any), calories per 100g. Users scan vertically; macros on a second line reduce mis-taps.

Keyboard behavior

Set returnKeyType="search" and dismiss keyboard on select so the log sheet appears immediately.

Handle Edge Cases

ScenarioImplementation
No results"Try a shorter name" + barcode scan CTA
Network offlineShow recents only + retry button
API 429Back off exponentially, show friendly message
Duplicate foodsPrefer verified_only=true on search when available

Performance Tips

  1. Memoize list rows with React.memo
  2. Limit suggest to 8 items
  3. Prefetch detail on highlight (optional)
  4. Batch logs locally, sync later if you add offline mode

Measuring Success

Track in analytics:

  • Time from screen open to food selected
  • Search queries with zero results
  • API errors per session

If median log time drops under 8 seconds, search UX is working.

Start free with Calorie API | API docs

Frequently Asked Questions

How do I add food search to a React Native app?

Add a debounced TextInput, call your backend suggest or search endpoint powered by Calorie API, render results in a FlatList, and cache recent selections in AsyncStorage.

Should I use autocomplete or full search in React Native?

Use suggest/autocomplete while the user types for speed. Call full search when they submit or need macro details on every row.

How many API calls does food search use per user?

With 300 ms debouncing and suggest endpoints, expect 1 to 3 API calls per logged meal. Recents caching removes repeat searches entirely.

What nutrition API is best for React Native food search?

Calorie API provides suggest, search, barcode, and per-100g macros with a free tier, making it ideal for React Native calorie tracking apps.

How do I show calories in search results?

Include calories per 100g from the search or suggest response. Scale to the user's portion after they select a food and enter grams.

← Back to all articles

Start building with the Calorie API

Get a free API key and access 4M+ foods with search, barcode lookup, and full macro data.