Search Food Item

You can utilize the searchForFood API to find food items based on their taste preferences by providing a search query.

How can I implement the searchForFood API and retrieve attribute data?

import { PassioSDK,} from '@passiolife/nutritionai-react-native-sdk-v2';


/**
     * Search the local database of foods with a given search term.
     * @param searchQuery - The search term to match against food item names.
     * @returns A `Promise` resolving to an array of food item names.
     */ 
    const searchFoods = await PassioSDK.searchForFood(query);

Example

To implement the searchForFood API and retrieve attribute data, you can modify the useFoodSearch custom hook to include these functionalities. Here's an updated version of your useFoodSearch hook with added comments and documentation annotations:

useFoodSearch


import { useState, useCallback, useEffect } from 'react';
import { useDebounce } from '../../utils/UseDebounce';
import {
  type PassioIDAttributes,
  PassioSDK,
  type FoodSearchResult,
} from '@passiolife/nutritionai-react-native-sdk-v2';

export interface FoodResult {
  passioIDAttributes: PassioIDAttributes;
  foodSearchResult: FoodSearchResult;
}

const useFoodSearch = () => {
  // State variables
  const [searchQuery, setSearchQuery] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);
  const [foodResults, setFoodResults] = useState<FoodResult[]>([]);
  const debouncedSearchTerm: string = useDebounce<string>(searchQuery, 300);

  // Clears search results and resets state
  const cleanSearch = useCallback(() => {
    setSearchQuery('');
    setFoodResults([]);
    setLoading(false);
  }, []);

  // Calls the search API based on the input value
  const callSearchApi = useCallback(
    async (query: string) => {
      // Check if the query is not empty
      if (query.length > 0) {
        // Set loading state to indicate the start of the search
        setLoading(true);

        try {
          // Fetch food results from the PassioSDK based on the query
          const searchFoods = await PassioSDK.searchForFood(query);

          // Process each search result, including fetching attributes
          const result: (FoodResult | null)[] = await Promise.all(
            searchFoods.map(async (item) => {
              // Retrieve attributes for the current food item
              const attribute = await PassioSDK.getAttributesForPassioID(
                item.passioID
              );

              // If attributes exist, create a FoodResult object, otherwise, return null
              if (attribute) {
                return {
                  passioIDAttributes: attribute,
                  foodSearchResult: item,
                };
              } else {
                return null;
              }
            })
          );

          // Filter out null values (where attributes were not found) and update state
          setFoodResults(result.filter((item): item is FoodResult => !!item));
        } catch (error) {
          // Handle errors, e.g., network issues or API failures
          setFoodResults([]);
        } finally {
          // Reset loading state to indicate the end of the search
          setLoading(false);
        }
      } else {
        // If the query is empty, reset the search state
        cleanSearch();
      }
    },
    [cleanSearch]
  );

  // Initiates a new search with the provided query
  const onSearchFood = useCallback(
    async (q: string) => {
      if (q.length > 0) {
        setSearchQuery(q);
        setFoodResults([]);
      } else {
        cleanSearch();
      }
    },
    [cleanSearch]
  );

  // Effect for handling debounced search term changes
  useEffect(() => {
    if (debouncedSearchTerm.length > 0) {
      callSearchApi(debouncedSearchTerm);
    } else {
      cleanSearch();
    }
  }, [callSearchApi, debouncedSearchTerm, cleanSearch]);

  return {
    loading,
    foodResults,
    searchQuery,
    cleanSearch,
    onSearchFood,
  };
};

export default useFoodSearch;

FoodSearch

import React from 'react';
import {
  ActivityIndicator,
  FlatList,
  Pressable,
  StyleSheet,
  Text,
  TextInput,
} from 'react-native';
import {
  PassioIconView,
  IconSize,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { SafeAreaView } from 'react-native-safe-area-context';
import useFoodSearch, { type FoodResult } from './useFoodSearch';

// FoodSearchScreen component
export const FoodSearchScreen = () => {
  // Destructure values from the custom hook
  const { searchQuery, onSearchFood, foodResults, loading } = useFoodSearch();

  // Get styles object from the searchStyle function
  const styles = searchStyle();

  // Function to render each item in the FlatList
  const renderItem = ({ item }: { item: FoodResult }) => {
    return (
      <Pressable style={styles.itemContainer}>
        <PassioIconView
          style={styles.itemIcon}
          config={{
            passioID: item.passioIDAttributes.passioID,
            iconSize: IconSize.PX90,
            passioIDEntityType: item.passioIDAttributes.entityType,
          }}
        />
        <Text style={styles.itemFoodName}>{item.foodSearchResult.name}</Text>
      </Pressable>
    );
  };

  // Display loading indicator when results are empty and loading is true
  const renderLoading = () => {
    return <>{loading ? <ActivityIndicator /> : null}</>;
  };

  // Render the component
  return (
    <SafeAreaView>
      {/* Search input */}
      <TextInput
        value={searchQuery}
        style={styles.textInput}
        placeholder="Search Food"
        onChangeText={onSearchFood}
      />

      {/* FlatList to display search results */}
      <FlatList
        data={foodResults}
        renderItem={renderItem}
        ListEmptyComponent={renderLoading}
        keyExtractor={(item, index) => item.result.passioID.toString() + index}
      />
    </SafeAreaView>
  );
};

// Styles for the component
const searchStyle = () =>
  StyleSheet.create({
    itemContainer: {
      padding: 12,
      backgroundColor: 'white',
      marginVertical: 4,
      marginHorizontal: 16,
      flexDirection: 'row',
      alignItems: 'center',
    },
    itemFoodName: {
      flex: 1,
      textTransform: 'capitalize',
      marginHorizontal: 8,
      fontSize: 16,
    },
    itemIcon: {
      height: 40,
      width: 40,
    },
    textInput: {
      backgroundColor: 'white',
      paddingHorizontal: 16,
      margin: 16,
    },
  });

Utility Methods

import { useState, useEffect } from 'react';

// Hook
// T is a generic type for value parameter, our case this will be string
export function useDebounce<T>(value: T, delay: number): T {
  // State and setters for debounced value
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
  useEffect(
    () => {
      // Update debounced value after delay
      const handler = setTimeout(() => {
        setDebouncedValue(value);
      }, delay);
      // Cancel the timeout if value changes (also on delay change or unmount)
      // This is how we prevent debounced value from updating if value is changed ...
      // .. within the delay period. Timeout gets cleared and restarted.
      return () => {
        clearTimeout(handler);
      };
    },
    [value, delay] // Only re-call effect if value or delay changes
  );
  return debouncedValue;
}

Result

Last updated