Recalculate the nutrition value based on serving size options.

Retrieve the PassioAttribute using the Passio ID. Within the food item, utilize the selectedQuantity and selectedServingUnit as the foundational values for obtaining nutrient values.

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.

Example

useRecalculateServingSize

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,
  };
};

RecalculateServingSize

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' },
});

Utility Methods

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;
}

Last updated