Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Require victory-native
and react-native-svg
npm package to display the chart.
yarn install victory-native
yarn install react-native-svg
Displaying the nutritional values for calories
, carbs
, proteins
, and fats
using the mockFood
data, which includes logged dates
for the respective food entries.
Retrieve mock data from this.
Obtain the utility function for fetching nutrition from the provided page.
import React from 'react';
import { Text, View, ScrollView } from 'react-native';
import {
VictoryAxis,
VictoryBar,
VictoryChart,
VictoryLabel,
VictoryTheme,
} from 'victory-native';
import { useMacrosNutrients } from './useMacrosNutrients';
import {
mergeAndSumMacroChartData,
type NutrientsTypes,
} from '../../../utils/PassioNutrient';
// Color mapping for each nutrient type
const NutrientColors = {
calories: '#FF4D61',
fat: '#00D795',
protein: '#0899FF',
carbs: '#00D795',
};
const MacrosNutrient = () => {
// Fetch nutrient data using custom hook
const { passioIDAttributesData, macros } = useMacrosNutrients();
// Function to render individual nutrient chart
const renderNutrientChart = (item: NutrientsTypes) => (
<View key={item}>
{/* Nutrient type label */}
<Text style={{ marginVertical: 8, alignSelf: 'center' }}>{item}</Text>
{/* Victory Chart for the nutrient */}
<VictoryChart theme={VictoryTheme.material} height={200}>
{/* X-axis configuration */}
<VictoryAxis
style={{
ticks: { stroke: 'transparent' },
grid: { stroke: 'none' },
axis: { stroke: '#F7F7F7' },
tickLabels: { fill: '#333333', fontSize: 14 },
axisLabel: {},
}}
/>
{/* Y-axis configuration */}
<VictoryAxis
dependentAxis
fixLabelOverlap
style={{
ticks: { stroke: 'transparent' },
axis: { stroke: 'none' },
grid: { stroke: '#F7F7F7' },
axisLabel: { padding: 0 },
tickLabels: { fill: '#333333', fontSize: 14 },
}}
/>
{/* Victory Bar chart for the nutrient */}
<VictoryBar
cornerRadius={8}
labelComponent={
<VictoryLabel
text={({ datum }) => Math.round(datum.value)}
dy={-10}
/>
}
alignment="middle"
style={{
data: {
borderTop: 24,
width: 20,
fill: NutrientColors[item],
},
}}
data={mergeAndSumMacroChartData(
passioIDAttributesData.filter((data) => data.type === item)
)}
x={(d) => d.label}
y="value"
/>
</VictoryChart>
</View>
);
// Render the component
return (
<ScrollView>
<View>
{/* Iterate over nutrient types and render charts */}
{macros.map(renderNutrientChart)}
</View>
</ScrollView>
);
};
export default MacrosNutrient;
import { useEffect, useMemo, useState } from 'react';
import {
getNutrientsFromAttribute,
type NutrientsTypes,
} from '../../../utils/PassioNutrient';
import { PassioSDK } from '@passiolife/nutritionai-react-native-sdk-v2';
import { DateTime } from 'luxon';
import { mockFood } from './mockFood';
export interface MacroChartData {
label: string;
value: number;
type: NutrientsTypes;
}
export const useMacrosNutrients = () => {
// State to store nutrient data
const macros: NutrientsTypes[] = useMemo(() => {
return ['calories', 'carbs', 'fat', 'protein'];
}, []);
const [passioIDAttributesData, setPassioIDAttributesData] = useState<
MacroChartData[]
>([]);
// Effect to fetch and process nutrient data on component mount
useEffect(() => {
// Temporary array to store processed nutrient data
const data: MacroChartData[] = [];
async function fetchData() {
// Create a copy of mockFood array to avoid modifying the original
const copyFood = [...mockFood];
// Fetch attributes for each PassioID and process nutrient data
await Promise.all(
copyFood.map(async (foodLog) => {
// Get attributes for a PassioID using PassioSDK
const attribute = await PassioSDK.getAttributesForPassioID(
foodLog.passioID
);
// If attributes are available, process the nutrients
if (attribute) {
// Get nutrients from the attribute for specific nutrient types
getNutrientsFromAttribute(attribute, macros).forEach((item) => {
// Convert timestamp to a formatted date and add the nutrient data to the array
data.push({
label: DateTime.fromJSDate(
new Date(foodLog.eventTimestamp)
).toFormat('dd/MM'),
value: item.unitMass?.value ?? 0,
type: item.name,
});
});
}
})
);
// Update the state with the processed nutrient data
setPassioIDAttributesData(data);
}
// Call the fetchData function
fetchData();
}, [macros]); // Empty dependency array to ensure the effect runs only once on mount
// Return the macros constant and the processed nutrient data
return {
macros,
passioIDAttributesData,
};
};
import type {
PassioFoodItem,
PassioIDAttributes,
PassioRecipe,
UnitMass,
} from '@passiolife/nutritionai-react-native-sdk-v3';
import type { MacroChartData } from '../screens/nutrients/macro/useMacrosNutrients';
// 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;
}
export const getNutrientsFromRecipe = (
recipe: PassioRecipe,
type: NutrientsTypes[]
): Nutrients[] => {
let nutrients: Nutrients[] = [];
recipe.foodItems.forEach((item) => {
nutrients.push(...getNutrients(item, type));
});
return mergeNutrients(nutrients);
};
export const getNutrientsFromAttribute = (
passioIDAttributes: PassioIDAttributes,
nutrientNames: NutrientsTypes[]
): Nutrients[] => {
if (passioIDAttributes.foodItem) {
return getNutrients(passioIDAttributes.foodItem, nutrientNames);
} else if (passioIDAttributes.recipe) {
return getNutrientsFromRecipe(passioIDAttributes.recipe, nutrientNames);
} else {
return [];
}
};
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);
};
export const getNutrientFromRecipe = (
recipe: PassioRecipe,
type: NutrientsTypes
): Nutrients[] => {
let nutrients: Nutrients[] = [];
recipe.foodItems.forEach((item) => {
const result = getNutrient(item, type);
if (result) {
nutrients.push(result);
}
});
return mergeNutrients(nutrients);
};
// Get a single nutrient from Passio Food Item
const getNutrient = (
foodItem: PassioFoodItem,
name: NutrientsTypes
): Nutrients | null => {
if (foodItem[name]) {
return {
name,
unitMass: foodItem[name] ?? undefined,
} as Nutrients;
} else {
return null;
}
};
export const micros: NutrientsTypes[] = [
'saturatedFat',
'transFat',
'polyunsaturatedFat',
'sodium',
'fiber',
'sugar',
'sugarAdded',
'vitaminD',
'iron',
'potassium',
'vitaminA',
'alcohol',
'sugarAlcohol',
'vitaminB12',
'vitaminB12Added',
'vitaminB6',
'vitaminE',
'vitaminEAdded',
'phosphorus',
'iodine',
'cholesterol',
];
export const macros: NutrientsTypes[] = ['calories', 'carbs', 'protein', 'fat'];
export const formatNutrient = (value?: number): number => {
try {
return Number((value ? value : 0).toFixed(2));
} catch (e) {
return -1;
}
};
export const mergeNutrients = (nutrientsArray: Nutrients[]): Nutrients[] => {
const mergedMap = new Map<NutrientsTypes, Nutrients>();
for (const nutrient of nutrientsArray) {
const { name, unitMass } = nutrient;
if (mergedMap.has(name)) {
// Nutrient with the same name already exists, add their values
const existingNutrient = mergedMap.get(name);
if (existingNutrient && unitMass?.value !== undefined) {
existingNutrient.unitMass = {
value:
(existingNutrient.unitMass?.value ?? 0) + (unitMass.value ?? 0),
unit: unitMass.unit ?? '',
};
}
} else {
// Nutrient with this name doesn't exist yet, add it to the map
mergedMap.set(name, {
...nutrient,
unitMass: unitMass ? { ...unitMass } : undefined,
});
}
}
// Convert the map back to an array
const mergedArray = Array.from(mergedMap.values());
return mergedArray;
};
export const mergeAndSumMacroChartData = (
data: MacroChartData[]
): MacroChartData[] => {
const mergedData: MacroChartData[] = [];
data.forEach((item) => {
const existingItem = mergedData.find(
(mergedItem) => mergedItem.label === item.label
);
if (existingItem) {
existingItem.value += item.value;
} else {
mergedData.push({ ...item });
}
});
return mergedData;
};
/* You can utilize the getNutrientsFromRecipe method,
declaring it as shown below, to retrieve specific
macro or micronutrient information from a recipe.
*/
useEffect(() => {
// Fetch Passio Food Item details when component mounts
const init = async () => {
const result = await PassioSDK.getAttributesForPassioID(passioID);
if (result && result.recipe && result) {
const macroNutrients = getNutrientsFromRecipe(result.recipe, macros);
const microsNutrients = getNutrientsFromRecipe(result.recipe, micros);
}
};
init();
}, [passioID]);
const micros: NutrientsTypes[] = [
'saturatedFat',
'transFat',
'polyunsaturatedFat',
'sodium',
'fiber',
'sugar',
'sugarAdded',
'vitaminD',
'iron',
'potassium',
'vitaminA',
'alcohol',
'sugarAlcohol',
'vitaminB12',
'vitaminB12Added',
'vitaminB6',
'vitaminE',
'vitaminEAdded',
'phosphorus',
'iodine',
'cholesterol',
];
const macros: NutrientsTypes[] = ['calories', 'carbs', 'protein', 'fat'];
export const getNutrientsFromRecipe = (
recipe: PassioRecipe,
type: NutrientsTypes[]
): Nutrients[] => {
let nutrients: Nutrients[] = [];
recipe.foodItems.forEach((item) => {
nutrients.push(...getNutrients(item, type));
});
return mergeNutrients(nutrients);
};
// Get multiple nutrients from Passio FoodItem
const getNutrients = (
foodItem: PassioFoodItem,
nutrientNames: NutrientsTypes[]
): Nutrients[] => {
return nutrientNames
.map((name) => {
// Handle special case for 'vitaminA'
if (name === 'vitaminA') {
return {
name,
unitMass: {
value: foodItem.vitaminA ?? 0,
unit: '',
},
};
}
// General case for other nutrients
return getNutrient(foodItem, name);
})
.filter((item): item is Nutrients => !!item);
};
export const getNutrientFromRecipe = (
recipe: PassioRecipe,
type: NutrientsTypes
): Nutrients[] => {
let nutrients: Nutrients[] = [];
recipe.foodItems.forEach((item) => {
const result = getNutrient(item, type);
if (result) {
nutrients.push(result);
}
});
return mergeNutrients(nutrients);
};
// Get a single nutrient from Passio Food Item
const getNutrient = (
foodItem: PassioFoodItem,
name: NutrientsTypes
): Nutrients | null => {
if (foodItem[name]) {
return {
name,
unitMass: foodItem[name] ?? undefined,
} as Nutrients;
} else {
return null;
}
};
import React, { useState, useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import {
PassioSDK,
type PassioID,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { Text } from 'react-native';
import { FlatList } from 'react-native';
import {
formatNutrient,
getNutrientsFromRecipe,
macros,
micros,
type Nutrients,
} from './MacroUtils';
export const MacroInfoRecipe = ({ passioID }: { passioID: PassioID }) => {
const [macroNutrients, setMacroNutrients] = useState<Nutrients[]>();
const [microNutrients, setMicroNutrients] = useState<Nutrients[]>();
useEffect(() => {
// Fetch Passio Food Item details when component mounts
const init = async () => {
const result = await PassioSDK.getAttributesForPassioID(passioID);
if (result && result.recipe && result) {
setMacroNutrients(getNutrientsFromRecipe(result.recipe, macros));
setMicroNutrients(getNutrientsFromRecipe(result.recipe, micros));
}
};
init();
}, [passioID]);
// Function to render individual nutrient information
const renderInformation = ({ item }: { item: Nutrients }) => {
const { name, unitMass } = item;
return (
<View style={styles.infoRow}>
<Text style={[styles.infoText, styles.infoTitle]}>{name}</Text>
<Text style={[styles.infoText, styles.infoValue]}>
{formatNutrient(unitMass?.value ?? 0)}
</Text>
</View>
);
};
return (
<View style={styles.container}>
{/* Display Macro Nutrients */}
<Text style={styles.heading}>Macro Nutrients</Text>
{macroNutrients && (
<FlatList data={macroNutrients} renderItem={renderInformation} />
)}
{/* Display Micro Nutrients */}
<Text style={styles.heading}>Micro Nutrients</Text>
{microNutrients && (
<FlatList data={microNutrients} renderItem={renderInformation} />
)}
</View>
);
};
// Styles for the component
export const styles = StyleSheet.create({
container: { flex: 1, paddingHorizontal: 16 },
heading: { fontSize: 18, fontWeight: '600', marginVertical: 16 },
infoRow: { flexDirection: 'row', marginVertical: 4 },
infoText: { fontSize: 16 },
infoTitle: { flex: 1 },
infoValue: {},
});
export const micros: NutrientsTypes[] = [
'saturatedFat',
'transFat',
'polyunsaturatedFat',
'sodium',
'fiber',
'sugar',
'sugarAdded',
'vitaminD',
'iron',
'potassium',
'vitaminA',
'alcohol',
'sugarAlcohol',
'vitaminB12',
'vitaminB12Added',
'vitaminB6',
'vitaminE',
'vitaminEAdded',
'phosphorus',
'iodine',
'cholesterol',
];
export const macros: NutrientsTypes[] = ['calories', 'carbs', 'protein', 'fat'];
export const formatNutrient = (value?: number): number => {
try {
return Number((value ? value : 0).toFixed(2));
} catch (e) {
return -1;
}
};
export const mergeNutrients = (nutrientsArray: Nutrients[]): Nutrients[] => {
const mergedMap = new Map<NutrientsTypes, Nutrients>();
for (const nutrient of nutrientsArray) {
const { name, unitMass } = nutrient;
if (mergedMap.has(name)) {
// Nutrient with the same name already exists, add their values
const existingNutrient = mergedMap.get(name);
if (existingNutrient && unitMass?.value !== undefined) {
existingNutrient.unitMass = {
value:
(existingNutrient.unitMass?.value ?? 0) + (unitMass.value ?? 0),
unit: unitMass.unit ?? '',
};
}
} else {
// Nutrient with this name doesn't exist yet, add it to the map
mergedMap.set(name, {
...nutrient,
unitMass: unitMass ? { ...unitMass } : undefined,
});
}
}
// Convert the map back to an array
const mergedArray = Array.from(mergedMap.values());
return mergedArray;
};
export interface MockFood {
passioID: string;
eventTimestamp: string;
}
export const mockFood: MockFood[] = [
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0023', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0065', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0065', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0023', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0065', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0023', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0065', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0023', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: '1603211580323', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: '1603211580323', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: '1603211580323', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: '1603211580323', eventTimestamp: '2024-01-31T06:28:55.179Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: '1603211580323', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: 'BEV0037', eventTimestamp: '2024-01-30T02:30:00.000Z' },
{ passioID: '1603211580323', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0023', eventTimestamp: '2024-01-29T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-28T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-28T06:43:00.000Z' },
{ passioID: 'VEG0018', eventTimestamp: '2024-01-28T06:43:00.000Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-28T06:43:00.000Z' },
{ passioID: 'BEV0065', eventTimestamp: '2024-01-28T06:43:00.000Z' },
{ passioID: 'VEG0023', eventTimestamp: '2024-01-28T06:43:00.000Z' },
{ passioID: 'BEV0036', eventTimestamp: '2024-01-28T06:43:00.000Z' },
];
PassioID
and PassioFoodItem
?/* You can utilize the getMacroNutrientsFormPassioFoodItem method,
declaring it as shown below, to retrieve specific
macro or micronutrient information from a recipe.
*/
useEffect(() => {
// Fetch Passio Food Item details when component mounts
const init = async () => {
const result = await PassioSDK.getAttributesForPassioID(passioID);
if (result && result.foodItem) {
// Set macro and micronutrients based on Passio Food Item
const macrosNutrients = getMacroNutrientsFormPassioFoodItem(result.foodItem);
const microsNutrients = getMicroNutrientsFormPassioFoodItem(result.foodItem);
}
};
init();
}, [passioID]);
const micros: NutrientsTypes[] = [
'saturatedFat',
'transFat',
'polyunsaturatedFat',
'sodium',
'fiber',
'sugar',
'sugarAdded',
'vitaminD',
'iron',
'potassium',
'vitaminA',
'alcohol',
'sugarAlcohol',
'vitaminB12',
'vitaminB12Added',
'vitaminB6',
'vitaminE',
'vitaminEAdded',
'phosphorus',
'iodine',
'cholesterol',
];
const macros: NutrientsTypes[] = ['calories', 'carbs', 'protein', 'fat'];
NutrientsTypes
, Nutrients
// 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;
}
getNutrient
method// 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;
}
};
getNutrients
method// 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);
};
import React, { useState, useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import {
PassioSDK,
type PassioID,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { Text } from 'react-native';
import { FlatList } from 'react-native';
import {
formatNutrient,
getMacroNutrientsFormPassioFoodItem,
getMicroNutrientsFormPassioFoodItem,
type Nutrients,
} from './MacroUtils';
export const MacroInfo = ({ passioID }: { passioID: PassioID }) => {
const [macroNutrients, setMacroNutrients] = useState<Nutrients[]>();
const [microNutrients, setMicroNutrients] = useState<Nutrients[]>();
useEffect(() => {
// Fetch Passio Food Item details when component mounts
const init = async () => {
const result = await PassioSDK.getAttributesForPassioID(passioID);
if (result && result.foodItem) {
// Set macro and micro nutrients based on Passio Food Item
setMacroNutrients(getMacroNutrientsFormPassioFoodItem(result.foodItem));
setMicroNutrients(getMicroNutrientsFormPassioFoodItem(result.foodItem));
}
};
init();
}, [passioID]);
// Function to render individual nutrient information
const renderInformation = ({ item }: { item: Nutrients }) => {
const { name, unitMass } = item;
return (
<View style={styles.infoRow}>
<Text style={[styles.infoText, styles.infoTitle]}>{name}</Text>
<Text style={[styles.infoText, styles.infoValue]}>
{formatNutrient(unitMass?.value ?? 0)}
</Text>
</View>
);
};
return (
<View style={styles.container}>
{/* Display Macro Nutrients */}
<Text style={styles.heading}>Macro Nutrients</Text>
{macroNutrients && (
<FlatList data={macroNutrients} renderItem={renderInformation} />
)}
{/* Display Micro Nutrients */}
<Text style={styles.heading}>Micro Nutrients</Text>
{microNutrients && (
<FlatList data={microNutrients} renderItem={renderInformation} />
)}
</View>
);
};
// Styles for the component
export const styles = StyleSheet.create({
container: { flex: 1, paddingHorizontal: 16 },
heading: { fontSize: 18, fontWeight: '600', marginVertical: 16 },
infoRow: { flexDirection: 'row', marginVertical: 4 },
infoText: { fontSize: 16 },
infoTitle: { flex: 1 },
infoValue: {},
});
import {
type PassioFoodItem,
type UnitMass,
} from '@passiolife/nutritionai-react-native-sdk-v2';
// 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 formatNutrient = (value?: number): number => {
return Number((value ?? 0).toFixed(2));
};
Display Micro nutrients progress using nutrients from PassioFoodItem and PassioRecipe.
Obtain the utility function for fetching nutrition from the provided page.
import React from 'react';
import { View } from 'react-native';
import { FlatList } from 'react-native';
import { unitForNutrient } from '../../../models';
import { microItemStyle } from './microView.styles';
import { useMicroNutrients, type MicroNutrition } from './useMicroNutrients';
import { Text } from 'react-native';
const MicrosView = () => {
// Fetch micro-nutrition data using the custom hook
const { microNutrition } = useMicroNutrients();
// Render function for each micro-nutrient item
const renderMicroItem = (micro: MicroNutrition) => {
const { title, recommended, achieved } = micro;
const unit = unitForNutrient(micro.id);
// Set userAchieved to 0 if achieved is null or undefined
const userAchieved =
achieved !== null && achieved !== undefined ? achieved : 0;
// Calculate remaining micro-nutrient value
const remain = Math.max(0, recommended - userAchieved);
return (
<View>
<View style={microItemStyle.infoContainer}>
<Text style={microItemStyle.title}>
{`${title} `}
<Text
style={microItemStyle.achieved}
>{`${userAchieved.toFixed(2)} ${unit}`}</Text>
</Text>
<View style={microItemStyle.recommendedContainer}>
<Text style={microItemStyle.recommendedValue}>
{`${Math.round(remain).toFixed(2)} ${unit}`}
</Text>
<Text style={microItemStyle.remainingText}>{'Remaining'}</Text>
</View>
</View>
<View style={microItemStyle.progressContainer}>
<View
style={[
{ flex: Math.round(userAchieved) },
microItemStyle.progress,
]}
/>
<View style={{ flex: Math.round(remain) }} />
</View>
</View>
);
};
return (
<FlatList
data={microNutrition}
contentContainerStyle={microItemStyle.list}
renderItem={({ item }: { item: MicroNutrition }) => renderMicroItem(item)}
keyExtractor={(__: MicroNutrition, index: number) => index.toString()}
extraData={microNutrition}
/>
);
};
// Wrap the MicrosView component with the withLoading higher-order component
export default MicrosView;
export const microItemStyle = StyleSheet.create({
infoContainer: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
},
title: {
color: COLORS.grey7,
paddingStart: 16,
fontSize: 15,
flex: 1,
fontWeight: '600',
alignItems: 'center',
},
achieved: {
color: "gray",
paddingHorizontal: 8,
fontSize: 15,
fontWeight: '400',
alignItems: 'center',
},
remainingText: {
color: "gray",
fontWeight: '400',
paddingHorizontal: 8,
alignItems: 'center',
},
recommendedContainer: {
flexDirection: 'row',
alignItems: 'center',
},
recommendedValue: {
minWidth: 70,
color: "gray",
alignSelf: 'center',
alignItems: 'center',
textAlign: 'center',
fontWeight: '600',
borderRadius: 28,
padding: 4,
},
progressContainer: {
flex: 1,
backgroundColor: "gray",
height: 10,
flexDirection: 'row',
marginHorizontal: 16,
borderRadius: 16,
},
progress: {
backgroundColor: "blue",
borderRadius: 16,
},
list: {
paddingBottom: 50,
},
});
import { useEffect, useState } from 'react';
import {
getNutrientsFromAttribute,
mergeNutrients,
micros,
type Nutrients,
type NutrientsTypes,
} from '../../../utils/PassioNutrient';
import { mockFood } from '../macro/mockFood';
import { PassioSDK } from '@passiolife/nutritionai-react-native-sdk-v2';
export interface MicroNutrition {
recommended: number;
title: string;
id: NutrientsTypes;
achieved?: number;
}
export const useMicroNutrients = () => {
// State to store micro-nutrition data
const [microNutrition, setMicroNutrition] = useState<MicroNutrition[]>([]);
// Food logs from mock data
const foodLogs = mockFood;
useEffect(() => {
// Function to initialize and fetch micro-nutrition data
async function init() {
// Array to store micro-nutrient data
let data: Nutrients[] = [];
// Iterate through food logs to fetch attributes and extract micro-nutrients
await Promise.all(
foodLogs.map(async (item) => {
const attribute = await PassioSDK.getAttributesForPassioID(
item.passioID
);
// If attributes are available, extract micro-nutrients
if (attribute) {
const nutrients = getNutrientsFromAttribute(attribute, micros);
data.push(...nutrients);
}
})
);
// Merge the micro-nutrient data
data = mergeNutrients(data);
// Define default micro-nutrients with recommended values
const defaultMicroNutrients: MicroNutrition[] = [
{
title: 'dietaryFiber',
id: 'fiber' as const,
recommended: 28,
},
{
title: 'sugar',
id: 'sugar' as const,
recommended: 50,
},
{
id: 'saturatedFat' as const,
title: 'SaturatedFat',
recommended: 20,
},
{
id: 'transFat' as const,
title: 'transFat',
recommended: 0,
},
{
id: 'polyunsaturatedFat' as const,
title: 'polyunsaturatedFat',
recommended: 22,
},
{
id: 'monounsaturatedFat' as const,
title: 'monounsaturatedFat',
recommended: 44,
},
{
id: 'cholesterol' as const,
title: 'cholesterol',
recommended: 300,
},
{
title: 'sodium',
id: 'sodium' as const,
recommended: 2300,
},
{
id: 'potassium' as const,
title: 'potassium',
recommended: 4700,
},
{
id: 'iron' as const,
title: 'iron',
recommended: 18,
},
{
id: 'magnesium' as const,
title: 'magnesium',
recommended: 420,
},
{
id: 'iodine' as const,
title: 'iodine',
recommended: 150,
},
{
id: 'vitaminA' as const,
title: 'vitaminA',
recommended: 900,
},
{
id: 'vitaminB6' as const,
title: 'vitaminB6',
recommended: 1.7,
},
{
id: 'vitaminB12' as const,
title: 'vitaminB12',
recommended: 2.4,
},
{
id: 'vitaminC' as const,
title: 'vitaminC',
recommended: 90,
},
{
id: 'vitaminD' as const,
title: 'vitaminD',
recommended: 20,
},
{
id: 'vitaminE' as const,
title: 'vitaminE',
recommended: 15,
},
].map((item) => ({
...item,
// Calculate achieved value by filtering the micro-nutrient data
achieved: data.find(
(allNutrimentItem) => allNutrimentItem.name === item.id
)?.unitMass?.value,
}));
// Update the state with the processed micro-nutrient data
setMicroNutrition(defaultMicroNutrients);
}
// Call the init function
init();
}, [foodLogs]); // Re-run the effect when foodLogs change
// Return the micro-nutrition data
return {
microNutrition,
};
};
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;
}