/**
* Look up the nutrition attributes for a given Passio ID.
* @param passioID - The Passio ID for the attributes query.
* @returns A `Promise` resolving to a `PassioIDAttributes` object if the record exists in the database or `null` if not.
*/
getAttributesForPassioID(passioID: PassioID): Promise<PassioIDAttributes | null>;
To generate the nutritional information for a recipe, the initial step involves obtaining the FoodItems and subsequently computing and aggregating the values for all nutrients.
import { useState, useEffect } from 'react';
import {
PassioSDK,
type PassioFoodItem,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import {
getNutrients,
getNutrientsFromRecipe,
mergeNutrients,
type Nutrients,
type NutrientsTypes,
} from '../../../utils/NutrientsUtils';
// Mock ingredients for testing purposes
const mockIngredients = ['1603211584879'];
// Nutrient types required for the recipe
const requireNutrientTypes: NutrientsTypes[] = [
'calories',
'carbs',
'protein',
'fat',
];
export const useRecipeEditor = () => {
// State variables for the recipe editor
const [recipeName, setRecipeName] = useState<string>('');
const [totalServings, setTotalServing] = useState<string>('1'); // or '0' if you prefer a numeric default
const [nutrients, setNutrients] = useState<Nutrients[]>();
const [ingredients, setIngredients] = useState<PassioFoodItem[]>();
// useEffect to initialize data
useEffect(() => {
async function init() {
try {
let collectNutrients: Nutrients[] = [];
let collectFoodItem: PassioFoodItem[] = [];
// Fetch attributes for each mock ingredient in parallel
await Promise.all(
mockIngredients?.map(async (item) => {
// Get attributes for the PassioID
const result = await PassioSDK.getAttributesForPassioID(item);
// Skip if result is null
if (result === null) {
return;
}
// Process result for recipe
if (result && result.recipe) {
collectNutrients.push(
...(result.recipe, requireNutrientTypes)
);
collectFoodItem.push(...result.recipe?.foodItems);
}
// Process result for food item
if (result.foodItem) {
collectFoodItem.push(result.foodItem);
collectNutrients.push(
...(result.foodItem, requireNutrientTypes)
);
}
return result;
})
);
// Merge collected nutrients and update state
const allMergeNutrients = (collectNutrients);
setNutrients(allMergeNutrients);
setIngredients(collectFoodItem);
} catch (error) {
// Handle errors if needed
}
}
// Call the init function when the component mounts
init();
}, []);
// Return state variables and functions for the recipe editor
return {
totalServings,
setRecipeName,
recipeName,
setTotalServing,
nutrients,
ingredients,
};
};
To access the methods related to retrieving nutrients, please refer to the following this link. For information on the Nutrients
class and NutrientsTypes
, kindly explore the provided resource.
To access the methods for getNutrientsFromRecipe
and mergeNutrients
, please refer to the following below link. The details on these methods and their functionality can be found in the provided resource.
import React from 'react';
import {
Dimensions,
FlatList,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import RecipeMacroView from './views/RecipeMacroView';
import {
PassioIconView,
type PassioFoodItem,
IconSize,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { useRecipeEditor } from './useRecipeEditor';
import { formatNutrient, type Nutrients } from '../../../utils/NutrientsUtils';
// Get the width of the window for styling purposes
const { width } = Dimensions.get('window');
// React component for the Recipe Editor screen
const RecipeEditorScreen = () => {
// Destructure state and functions from useRecipeEditor hook
const {
recipeName,
setRecipeName,
nutrients,
setTotalServing,
totalServings,
ingredients,
} = useRecipeEditor();
// Render individual ingredient in the FlatList
const renderIngredients = ({ item }: { item: PassioFoodItem }) => {
return (
<View style={styles.ingredientContainer}>
<PassioIconView
style={styles.passioIcon}
config={{
passioID: item.passioID,
iconSize: IconSize.PX180,
passioIDEntityType: item.entityType,
}}
/>
<Text style={styles.itemIngredientFoodName}>{item.name}</Text>
</View>
);
};
// Render individual nutrient in the FlatList
const renderNutrients = ({ item }: { item: Nutrients }) => {
const nutrientsPerServing =
(item.unitMass?.value ?? 0) /
(totalServings.length > 0 ? Number(totalServings) ?? 0 : 1);
return (
<RecipeMacroView
title={item.name}
value={formatNutrient(nutrientsPerServing)}
/>
);
};
// Return the JSX for the RecipeEditorScreen component
return (
<ScrollView style={styles.container}>
{/* Input for Recipe Name */}
<View style={styles.inputContainer}>
<Text style={styles.inputTitle}>Recipe Name</Text>
<TextInput
onChangeText={(text) => setRecipeName(text)}
placeholder="My Recipe"
testID="testMyRecipe"
style={styles.textInput}
value={recipeName}
/>
</View>
{/* Input for Total Servings */}
<View style={styles.inputContainer}>
<Text style={styles.inputTitle}>
<Text style={styles.inputTitle}>Total servings </Text>
<Text style={styles.hintText}>(how many does it serve?)</Text>
</Text>
<TextInput
value={totalServings?.toString()}
testID={'testEditNumberOfServing'}
onChangeText={(text) => setTotalServing(text)}
placeholder="Number of servings"
style={styles.textInput}
keyboardType="numeric"
/>
</View>
{/* Section for Macros Per Serving */}
<View style={styles.header}>
<Text style={styles.titleText}>Macros Per Serving</Text>
</View>
<FlatList data={nutrients} renderItem={renderNutrients} />
{/* Divider Line */}
<View style={styles.line} />
{/* Section for Ingredients */}
<View style={styles.header}>
<Text style={styles.titleText}>Ingredients</Text>
</View>
<FlatList
data={ingredients}
renderItem={renderIngredients}
keyExtractor={(item) => item.passioID}
/>
</ScrollView>
);
};
// Styles for the RecipeEditorScreen component
const styles = StyleSheet.create({
container: {
flex: 1,
},
inputContainer: {
marginTop: 24,
marginLeft: 20,
},
// Styles for PassioIconView
passioIcon: {
height: 40,
width: 40,
},
// Styles for individual ingredient container
ingredientContainer: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: 'white',
marginHorizontal: 16,
marginVertical: 8,
},
// Styles for TextInput
textInput: {
borderBottomWidth: 1,
marginTop: 10,
height: 40,
width: width / 1.1 - 5,
borderBottomColor: 'gray',
},
// Styles for ingredient text
itemIngredientFoodName: {
fontSize: 16,
marginHorizontal: 8,
textTransform: 'capitalize',
},
// Styles for section headers
header: {
marginHorizontal: 16,
marginTop: 32,
},
// Styles for divider line
line: {
backgroundColor: 'gray',
marginHorizontal: 16,
marginTop: 16,
height: 1,
},
// Styles for section titles
titleText: {
fontWeight: '600',
fontSize: 15,
},
// Styles for input titles
inputTitle: {
fontWeight: '600',
fontSize: 15,
color: 'black',
},
// Styles for input hint text
hintText: {
fontWeight: '400',
fontSize: 15,
color: 'black',
},
});
export default RecipeEditorScreen;
import { StyleSheet, Text, View } from 'react-native';
import React from 'react';
const RecipeMacroView = ({
title,
value,
}: {
title: string;
value: number;
}) => {
return (
<View style={styles.ingredientStyle}>
<Text style={styles.titleText}>{title}</Text>
<Text style={styles.valueText}>{value.toFixed(0)}</Text>
</View>
);
};
const styles = StyleSheet.create({
ingredientStyle: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginHorizontal: 16,
marginTop: 12,
},
titleText: {
fontWeight: '600',
fontSize: 15,
color: 'black',
},
valueText: {
fontWeight: '400',
fontSize: 15,
color: 'black',
},
});
export default RecipeMacroView;