Recalculate the nutrition value based on serving size options.
Last updated
Last updated
Some ServingUnits are included within the food item as servings options from SDK, each accompanied by a value and unit name. For example, an apple might have default servings like large with a value of 200, medium with a value of 172, and small with a value of 157.
import {
PassioSDK,
type ServingUnit,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { useEffect, useRef, useState } from 'react';
import {
calculateComputedWeightAmount,
getMacroNutrientsFormPassioFoodItem,
toFixed,
type Nutrients,
} from './recalculateUtils';
export interface RecalculateServingSizeProps {
passioID: string;
}
export const useRecalculateServingSize = ({
passioID,
}: RecalculateServingSizeProps) => {
// State to manage user input quantity
const [userInputQuantity, setUserInputQuantity] = useState('');
// State to manage selected serving value
const [selectedServingValue, setSelectedServingValue] = useState(0);
// State to manage selected serving unit
const [selectedServingUnit, setSelectedServingUnit] = useState('');
// State to store nutrients data
const [nutrients, setNutrition] = useState<Nutrients[]>();
// State to store serving units from PassioSDK
const [sdkServingUnits, setSDKServingUnits] = useState<ServingUnit[]>();
// Ref to store the base serving value for calculations
const sdkBaseServingValueRef = useRef(0);
// useEffect to initialize data on component mount
useEffect(() => {
// Function to initialize data from the PassioSDK
const initialize = async () => {
try {
const { foodItem } =
(await PassioSDK.getAttributesForPassioID(passioID)) || {};
if (!foodItem) return;
const { servingUnits, selectedQuantity, selectedUnit } = foodItem;
setSDKServingUnits(servingUnits);
setNutrition(getMacroNutrientsFormPassioFoodItem(foodItem));
const baseServingValue = calculateComputedWeightAmount(
selectedQuantity,
servingUnits,
selectedUnit
);
sdkBaseServingValueRef.current = baseServingValue;
setSelectedServingValue(baseServingValue);
setUserInputQuantity(selectedQuantity.toString());
setSelectedServingUnit(selectedUnit);
} catch (error) {
console.error('Error initializing:', error);
}
};
// Call the initialize function
initialize();
}, [passioID]);
// Function to handle selection of a serving unit
const onSelectServingItem = (servingUnit: ServingUnit) => {
setSelectedServingValue(servingUnit.value);
setSelectedServingUnit(servingUnit.unitName);
};
// Function to calculate updated nutrition value based on user input
const getUpdatedNutritionValue = (item: Nutrients) => {
const nutritionValue = item.unitMass?.value ?? 0;
const ratio = calculateRatio(nutritionValue);
const userInputServingSize =
userInputQuantity.length > 0 ? Number(userInputQuantity) : 1;
return toFixed(ratio * userInputServingSize);
};
// Function to calculate the ratio separately
const calculateRatio = (nutritionValue: number): number => {
return (
(nutritionValue * selectedServingValue) / sdkBaseServingValueRef.current
);
};
// Return the necessary values and functions
return {
nutrients,
onSelectServingItem,
sdkServingUnits,
selectedServingUnit,
getUpdatedNutritionValue,
setUserInputQuantity,
userInputQuantity,
};
};
import React from 'react';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { toFixed } from './recalculateUtils';
import { FlatList } from 'react-native';
import { useRecalculateServingSize } from './useRecaculateServingSize';
export interface RecalculateServingSizeProps {
passioID: string;
}
export const RecalculateServingSize = ({
passioID,
}: RecalculateServingSizeProps) => {
// Destructuring values and functions from useRecalculateServingSize hook
const {
nutrients,
onSelectServingItem,
sdkServingUnits,
selectedServingUnit,
getUpdatedNutritionValue,
setUserInputQuantity,
userInputQuantity,
} = useRecalculateServingSize({
passioID: passioID,
});
return (
<View style={styles.container}>
{/* FlatList to display updated nutrition values */}
<FlatList
data={nutrients}
extraData={userInputQuantity}
renderItem={({ item }) => (
<View style={styles.nutrition}>
<Text style={styles.nutritionLabel}>{item.name}</Text>
<Text>
{getUpdatedNutritionValue(item)}
{item.unitMass?.unit}
</Text>
</View>
)}
/>
{/* Section to input total serving quantity */}
<View style={styles.nutritionTotalServing}>
<Text style={styles.nutritionLabel}>Total Serving</Text>
<TextInput
style={styles.totalServingInput}
placeholder="Total Serving"
keyboardType="numeric"
value={userInputQuantity.trim().toString()}
onChangeText={(value) => {
setUserInputQuantity(value.trim());
}}
/>
</View>
{/* FlatList to display serving units */}
<FlatList
data={sdkServingUnits}
horizontal
renderItem={({ item }) => (
<Pressable
onPress={() => {
onSelectServingItem(item);
}}
>
<Text
style={[
styles.servingUnit,
selectedServingUnit === item.unitName &&
styles.selectedServingUnit,
]}
>
{`${item.unitName} ${toFixed(item.value)}`}
</Text>
</Pressable>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
nutrition: { flex: 1, flexDirection: 'row', padding: 16 },
servingUnit: { flexDirection: 'row', padding: 16 },
nutritionTotalServing: {
marginVertical: 16,
},
totalServingInput: { padding: 16, backgroundColor: 'white' },
selectedServingUnit: {
backgroundColor: 'blue',
color: 'white',
borderRadius: 24,
},
nutritionLabel: { flex: 1, textTransform: 'capitalize' },
});
import type {
PassioFoodItem,
ServingUnit,
UnitMass,
} from '@passiolife/nutritionai-react-native-sdk-v2';
// Define the types of nutrients
export type NutrientsTypes =
| 'calories'
| 'protein'
| 'carbs'
| 'fat'
| 'saturatedFat'
| 'transFat'
| 'polyunsaturatedFat'
| 'sodium'
| 'fiber'
| 'sugar'
| 'sugarAdded'
| 'vitaminD'
| 'iron'
| 'potassium'
| 'vitaminA'
| 'vitaminC'
| 'alcohol'
| 'sugarAlcohol'
| 'vitaminB12'
| 'vitaminB12Added'
| 'vitaminB6'
| 'vitaminE'
| 'vitaminEAdded'
| 'phosphorus'
| 'iodine'
| 'cholesterol';
// Define the structure of individual nutrients
export interface Nutrients {
unitMass?: UnitMass;
name: NutrientsTypes;
}
// Get a single nutrient from Passio Food Item
export const getNutrient = (
foodItem: PassioFoodItem,
name: NutrientsTypes
): Nutrients | null => {
if (foodItem[name]) {
return {
name,
unitMass: foodItem[name] ?? undefined,
} as Nutrients;
} else {
return null;
}
};
// Get multiple nutrients from Passio FoodItem
const getNutrients = (
foodItem: PassioFoodItem,
nutrientNames: NutrientsTypes[]
): Nutrients[] => {
return nutrientNames
.map((name) => {
// General case for other nutrients
return getNutrient(foodItem, name);
})
.filter((item): item is Nutrients => !!item);
};
// Get micro nutrients from Passio FoodItem
export const getMicroNutrientsFormPassioFoodItem = (
foodItem: PassioFoodItem
): Nutrients[] => {
const microNutrientNames: NutrientsTypes[] = [
'saturatedFat',
'transFat',
'polyunsaturatedFat',
'sodium',
'fiber',
'sugar',
'sugarAdded',
'vitaminD',
'iron',
'potassium',
'vitaminA',
'alcohol',
'sugarAlcohol',
'vitaminB12',
'vitaminB12Added',
'vitaminB6',
'vitaminE',
'vitaminEAdded',
'phosphorus',
'iodine',
'cholesterol',
];
return getNutrients(foodItem, microNutrientNames);
};
// Get micro nutrients from Passio FoodItem
export const getMacroNutrientsFormPassioFoodItem = (
foodItem: PassioFoodItem
): Nutrients[] => {
const macros: NutrientsTypes[] = ['calories', 'carbs', 'protein', 'fat'];
return getNutrients(foodItem, macros);
};
// Function to format nutrient value
export const toFixed = (value?: number): number => {
return Number((value ?? 0).toFixed(2));
};
export function calculateComputedWeightAmount(
qty: number,
servingUnits: ServingUnit[],
unit: string
) {
const result = qty * calculateMassOfServingUnit(servingUnits, unit);
return result < 10 ? result : Math.ceil(result);
}
export function calculateMassOfServingUnit(
servingUnits: ServingUnit[],
selectedUnit: string
): number {
let unit = 1;
servingUnits?.forEach((value) => {
if (value.unitName === selectedUnit) {
unit = value.value;
}
});
return unit;
}