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 pattern | User reaction |
|---|---|
| Search on every keystroke | Laggy keyboard, wasted API calls |
| No loading indicator | "Is it broken?" |
| Generic error toast | Uninstall |
| No recent foods | Re-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:
| Endpoint | Best for |
|---|---|
/search/suggest?q=chi | Typeahead dropdown (fast, short names) |
/search/foods?q=chicken+breast&limit=20 | Full 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
| Scenario | Implementation |
|---|---|
| No results | "Try a shorter name" + barcode scan CTA |
| Network offline | Show recents only + retry button |
| API 429 | Back off exponentially, show friendly message |
| Duplicate foods | Prefer verified_only=true on search when available |
Performance Tips
- Memoize list rows with
React.memo - Limit suggest to 8 items
- Prefetch detail on highlight (optional)
- 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.
Related Guides
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.
