Integrate Suggestions

The API below is used for food search.

/**
   * fetch a suggestions for particular meal time  'breakfast' | 'lunch' | 'dinner' | 'snack' and returning results.
   * @param mealTime - 'breakfast' | 'lunch' | 'dinner' | 'snack',
   * @returns A `Promise` resolving to a `PassioFoodDataInfo` array if the record exists in the database or `null` if not.
   */
  fetchSuggestions(
    mealTime: PassioMealTime
  ): Promise<PassioFoodDataInfo[] | null>

  /**
   * Data info of the search food with a given search result.
   * @param passioFoodDataInfo - Provide `PassioFoodDataInfo` object get `PassioFoodItem` detail.
   * @returns A `Promise` resolving to `PassioFoodItem` detail.
   */
  fetchFoodItemForDataInfo(
    passioFoodDataInfo: PassioFoodDataInfo
  ): Promise<PassioFoodItem | null>

Example

useSuggestions

import { useState, useCallback, useEffect } from 'react'
import {
  PassioSDK,
  type PassioFoodDataInfo,
  PassioMealTime,
} from '@passiolife/nutritionai-react-native-sdk-v3'
import type { Props } from './FoodSuggestion'

const useSuggestions = ({ onFoodDetail }: Props) => {
  // State variables
  const [mealTime, setMealTime] = useState<PassioMealTime>('breakfast')
  const [loading, setLoading] = useState<boolean>(false)
  const [foodResults, setFoodResults] = useState<PassioFoodDataInfo[] | null>()
  const mealTimes: PassioMealTime[] = ['breakfast', 'dinner', 'lunch', 'snack']

  // Effect for handling debounced search term changes
  useEffect(() => {
    async function init() {
      try {
        setLoading(true)
        setFoodResults([])
        // Fetch food results from the PassioSDK based on the query
        const searchFoods = await PassioSDK.fetchSuggestions(mealTime)
        setFoodResults(searchFoods)
      } 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)
      }
    }
    init()
  }, [mealTime])

  const onResultItemPress = useCallback(
    async (foodSearchResult: PassioFoodDataInfo) => {
      const result = await PassioSDK.fetchFoodItemForDataInfo(foodSearchResult)
      if (result) {
        onFoodDetail(result)
      }
    },
    [onFoodDetail]
  )

  const onChangeMeal = (mealTime: PassioMealTime) => {
    setMealTime(mealTime)
  }

  return {
    foodResults,
    mealTimes,
    onChangeMeal,
    loading,
    onResultItemPress,
    mealTime,
  }
}

export default useSuggestions

FoodSuggestion

import React from 'react'
import {
  ActivityIndicator,
  FlatList,
  SafeAreaView,
  StyleSheet,
  Text,
  TouchableOpacity,
  Image,
  View,
} from 'react-native'
import {
  PassioIconView,
  IconSize,
  PassioFoodDataInfo,
  PassioFoodItem,
  PassioMealTime,
} from '@passiolife/nutritionai-react-native-sdk-v3'
import useSuggestions from './useSuggestion'

export interface Props {
  onClose: () => void
  onFoodDetail: (passioFoodItem: PassioFoodItem) => void
}

export const FoodSuggestion = (props: Props) => {
  const styles = suggestionStyle()

  // Destructure values from the custom hook
  const {
    foodResults,
    loading,
    onResultItemPress,
    mealTime,
    onChangeMeal,
    mealTimes,
  } = useSuggestions(props)

  // Function to render each item in the FlatList
  const renderSuggestionItem = ({ item }: { item: PassioFoodDataInfo }) => {
    return (
      <TouchableOpacity
        style={styles.itemContainer}
        onPress={() => onResultItemPress(item)}
      >
        <View style={styles.itemIconContainer}>
          <PassioIconView
            style={styles.itemIcon}
            config={{
              passioID: item.iconID,
              iconSize: IconSize.PX360,
            }}
          />
        </View>
        <View>
          <Text style={styles.itemFoodName}>{item.foodName}</Text>
          <Text style={styles.itemBrandName}>
            {Math.round(item.nutritionPreview?.calories ?? 0) + ' kcal'}
            {item.brandName ? ', ' + item.brandName : ''}
          </Text>
        </View>
      </TouchableOpacity>
    )
  }
  const renderMealTime = ({ item }: { item: PassioMealTime }) => {
    return (
      <TouchableOpacity
        style={styles.mealTimeContainer}
        onPress={() => onChangeMeal(item)}
      >
        <Text
          style={[
            styles.mealTime,
            item === mealTime && styles.selectedMealTime,
          ]}
        >
          {item}
        </Text>
      </TouchableOpacity>
    )
  }

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

  // Render the component
  return (
    <SafeAreaView style={styles.body}>
      <View style={styles.closeButton}>
        <TouchableOpacity onPress={props.onClose}>
          <Image
            style={styles.closeText}
            source={require('../assets/back.png')}
          />
        </TouchableOpacity>
      </View>

      <FlatList
        data={foodResults}
        contentContainerStyle={styles.list}
        renderItem={renderSuggestionItem}
        ListEmptyComponent={renderLoading}
        ListHeaderComponent={() => {
          return (
            <FlatList data={mealTimes} renderItem={renderMealTime} horizontal />
          )
        }}
        keyExtractor={(item, index) => item.iconID.toString() + index}
      />
    </SafeAreaView>
  )
}

// Styles for the component
const suggestionStyle = () =>
  StyleSheet.create({
    closeButton: {},
    list: {
      marginTop: 16,
    },
    closeText: {
      margin: 16,
      height: 24,
      width: 24,
    },
    itemContainer: {
      padding: 12,
      flex: 1,
      marginVertical: 4,
      marginHorizontal: 16,
      backgroundColor: 'white',
      flexDirection: 'row',
      borderRadius: 24,
    },
    mealTimeContainer: {
      marginStart: 16,
      backgroundColor: 'white',
      flexDirection: 'row',
      borderRadius: 24,
      marginBottom: 16,
      overflow: 'hidden',
    },
    mealTime: {
      textTransform: 'capitalize',
      paddingVertical: 12,
      paddingHorizontal: 12,
      fontSize: 16,
    },
    itemFoodName: {
      flex: 1,
      textTransform: 'capitalize',
      marginHorizontal: 8,
      fontSize: 16,
    },

    selectedMealTime: {
      color: 'white',
      backgroundColor: 'blue',
    },
    itemBrandName: {
      flex: 1,
      textTransform: 'capitalize',
      marginHorizontal: 8,
      fontSize: 12,
    },

    itemAlternativeContainer: {
      overflow: 'hidden',
    },
    alternativeContainer: {
      marginStart: 16,
      alignItems: 'center',
      overflow: 'hidden',
      alignSelf: 'center',
      backgroundColor: 'rgba(238, 242, 255, 1)',
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 0.1 },
      shadowOpacity: 0.5,
      shadowRadius: 0.5,
      marginVertical: 2,
      marginBottom: 14,
      elevation: 5,
      borderRadius: 24,
    },
    itemAlternativeName: {
      textTransform: 'capitalize',
      paddingVertical: 8,
      paddingHorizontal: 16,
    },
    itemIconContainer: {
      height: 46,
      width: 46,
      borderRadius: 30,
      overflow: 'hidden',
    },
    itemIcon: {
      height: 46,
      width: 46,
    },
    textInput: {
      backgroundColor: 'white',
      paddingHorizontal: 16,
      padding: 12,
      color: 'black',
      fontWeight: '500',
      fontSize: 16,
      marginHorizontal: 16,
    },
    body: {
      backgroundColor: 'rgba(242, 245, 251, 1)',
      flex: 1,
    },
  })

Last updated