uday
uday

Reputation: 789

How to aggregate values from a list and display in react native app?

I have a list which I get from firebase and displaying those values in the UI using SwipeListView.

The list data contains details like: coin price, count of coins purchased, total cost of the coins as price,count and amount. And some other fields.

Now, I want to make aggregate of all the coins and display in the text field. But the aggregation I am not able to set.

I tried creating two variables and setting those using hooks but getting error as

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

Can you please suggest the approach please. Below is my code.

import React, { useEffect, useState } from "react";
import {
  FlatList,
  Keyboard,
  Text,
  TextInput,
  TouchableOpacity,
  View,
  StyleSheet,
} from "react-native";
import { Avatar, Button, Card, Title, Paragraph } from "react-native-paper";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { Icon } from "react-native-elements";

export default function OrderList(props) {
  const LeftContent = (props) => <Avatar.Icon {...props} icon="folder" />;

  const [orderText, setOrderText] = useState("");
  const [orders, setOrders] = useState([]);

  const orderRef = firebase.firestore().collection("orders");
  const userID = props.route.params.userID;
  const coin = props.route.params.coin;
  let totalCost = 0;
  let totalCount = 0;

  const [totalCost, setTotalCost] = useState[""];
  const [totalCount, setTotalCount] = useState[""];
  const [averageValue, setAverageValue] = useState("");
  //averageValue = (totalCost / totalCount).toString();

  useEffect(() => {
    orderRef
      .where("authorID", "==", userID)
      .where("name", "==", coin)
      .orderBy("createdAt")
      .onSnapshot(
        (querySnapshot) => {
          const newOrders = [];
          querySnapshot.forEach((doc) => {
            const order = doc.data();
            order.id = doc.id;
            newOrders.push(order);
          });
          setOrders(newOrders);
        },
        (error) => {
          console.log(error);
        }
      );
  }, []);

  useEffect(() => {
    setAverageValue(
      (parseFloat(totalCost) / parseFloat(totalCount)).toString()
    );
  }, [totalCount, totalCost]);

  const onAddButtonPress = () => {
    props.navigation.navigate("CreateOrder", {
      coin: coin,
      userID: userID,
      orderRef,
    });
  };

  const renderOrder = ({ item, index }) => {
    console.log("----------------------");
    console.log(item.createdAt.toDate().toString());
    console.log("----------------------");
    setTotalCost(parseFloat(totalCost) + parseFloat(item.price));
    setTotalCount(parseFloat(totalCount) + parseFloat(item.count));
    console.log(totalCost);
    console.log(totalCount);

    return (
      <View style={styles1.rowFront}>
        <Text>
          {index}. {item.price} {item.amount} {item.count}
          {"\n" + item.createdAt.toDate().toString()}
        </Text>

        <Icon name={"flight-takeoff"} />
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
        <Text style={styles.buttonText}>Click here to create new order..</Text>
      </TouchableOpacity>
      <Text>Average: {averageValue}</Text>
      {orders && (
        <SwipeListView
          data={orders}
          keyExtractor={(item) => item.id}
          renderItem={renderOrder}
          removeClippedSubviews={true}
        />
      )}
    </View>
  );
}

const styles1 = StyleSheet.create({
  rowFront: {
    alignItems: "center",
    backgroundColor: "#FFF",
    borderBottomWidth: 0.25,
    justifyContent: "center",
    height: 50,
  },
  rowBack: {
    alignItems: "center",
    backgroundColor: "#DDD",
    flex: 1,
    flexDirection: "row",
    justifyContent: "space-between",
    paddingLeft: 15,
  },
  backRightBtn: {
    alignItems: "center",
    bottom: 0,
    justifyContent: "center",
    position: "absolute",
    top: 0,
    width: 75,
    backgroundColor: "red",
    right: 0,
  },
});

Modified code with normal variables and useEffect linked to them to change average.

import React, { useEffect, useState } from "react";
import {
  FlatList,
  Keyboard,
  Text,
  TextInput,
  TouchableOpacity,
  View,
  StyleSheet,
} from "react-native";
import { Avatar, Button, Card, Title, Paragraph } from "react-native-paper";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { Icon } from "react-native-elements";

export default function OrderList(props) {
  const LeftContent = (props) => <Avatar.Icon {...props} icon="folder" />;

  const [orderText, setOrderText] = useState("");
  const [orders, setOrders] = useState([]);

  const orderRef = firebase.firestore().collection("orders");
  const userID = props.route.params.userID;
  const coin = props.route.params.coin;
  let totalCost = 0;
  let totalCount = 0;

  //const [totalCost, setTotalCost] = useState[""];
  //const [totalCount, setTotalCount] = useState[""];
  const [averageValue, setAverageValue] = useState("");
  //averageValue = (totalCost / totalCount).toString();

  useEffect(() => {
    orderRef
      .where("authorID", "==", userID)
      .where("name", "==", coin)
      .orderBy("createdAt")
      .onSnapshot(
        (querySnapshot) => {
          const newOrders = [];
          querySnapshot.forEach((doc) => {
            const order = doc.data();
            order.id = doc.id;
            newOrders.push(order);
          });
          setOrders(newOrders);
        },
        (error) => {
          console.log(error);
        }
      );
  }, []);

  useEffect(() => {
    setAverageValue(
      (parseFloat(totalCost) / parseFloat(totalCount)).toString()
    );
  }, [totalCount, totalCost]);

  const onAddButtonPress = () => {
    props.navigation.navigate("CreateOrder", {
      coin: coin,
      userID: userID,
      orderRef,
    });
  };

  const renderOrder = ({ item, index }) => {
    console.log("----------------------");
    console.log(item.createdAt.toDate().toString());
    console.log("----------------------");
    //setTotalCost(parseFloat(totalCost) + parseFloat(item.price));
    //setTotalCount(parseFloat(totalCount) + parseFloat(item.count));
    totalCost = parseFloat(totalCost) + parseFloat(item.price);
    totalCount = parseFloat(totalCount) + parseFloat(item.count);

    console.log(totalCost);
    console.log(totalCount);

    return (
      <View style={styles1.rowFront}>
        <Text>
          {index}. {item.price} {item.amount} {item.count}
          {"\n" + item.createdAt.toDate().toString()}
        </Text>

        <Icon name={"flight-takeoff"} />
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
        <Text style={styles.buttonText}>Click here to create new order..</Text>
      </TouchableOpacity>
      <Text>Average: {averageValue}</Text>
      {orders && (
        <SwipeListView
          data={orders}
          keyExtractor={(item) => item.id}
          renderItem={renderOrder}
          removeClippedSubviews={true}
        />
      )}
    </View>
  );
}

const styles1 = StyleSheet.create({
  rowFront: {
    alignItems: "center",
    backgroundColor: "#FFF",
    borderBottomWidth: 0.25,
    justifyContent: "center",
    height: 50,
  },
  rowBack: {
    alignItems: "center",
    backgroundColor: "#DDD",
    flex: 1,
    flexDirection: "row",
    justifyContent: "space-between",
    paddingLeft: 15,
  },
  backRightBtn: {
    alignItems: "center",
    bottom: 0,
    justifyContent: "center",
    position: "absolute",
    top: 0,
    width: 75,
    backgroundColor: "red",
    right: 0,
  },
});

.

thanks @jnpdx it is working now. Based on your inputs, my changes.

import React, { useEffect, useState } from "react";
import {
  FlatList,
  Keyboard,
  Text,
  TextInput,
  TouchableOpacity,
  View,
  StyleSheet,
} from "react-native";
import { Avatar, Button, Card, Title, Paragraph } from "react-native-paper";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { Icon } from "react-native-elements";
import { createIconSetFromFontello } from "@expo/vector-icons";

export default function OrderList(props) {
  const LeftContent = (props) => <Avatar.Icon {...props} icon="folder" />;

  const [orderText, setOrderText] = useState("");
  const [orders, setOrders] = useState([]);

  const orderRef = firebase.firestore().collection("orders");
  const userID = props.route.params.userID;
  const coin = props.route.params.coin;

  //averageValue = (totalCost / totalCount).toString();
  const [averageValue, setAverageValue] = useState("");

  useEffect(() => {
    orderRef
      .where("authorID", "==", userID)
      .where("name", "==", coin)
      .orderBy("createdAt")
      .onSnapshot(
        (querySnapshot) => {
          const newOrders = [];
          querySnapshot.forEach((doc) => {
            const order = doc.data();
            order.id = doc.id;
            newOrders.push(order);
          });
          setOrders(newOrders);
        },
        (error) => {
          console.log(error);
        }
      );
  }, []);

  useEffect(() => {
    //calculate and set anything like totalCost, averageValue, etc here
    console.log("---came to orders effect---");
    //console.log(orders);
    let totalCost = 0;
    let totalCount = 0;
    orders.forEach((item, index) => {
      console.log(item);
      console.log(index);
      totalCost += parseFloat(item.amount);
      totalCount += parseFloat(item.count);
    });
    setAverageValue((totalCost / totalCount).toString());
  }, [orders]);
  /*
  useEffect(() => {
    let avg = (parseFloat(totalCost) / parseFloat(totalCount)).toString();
    console.log("Avg:" + avg);
    setAverageValue(avg);
  }, [totalCount, totalCost]);
*/
  const onAddButtonPress = () => {
    props.navigation.navigate("CreateOrder", {
      coin: coin,
      userID: userID,
      orderRef,
    });
  };

  const renderOrder = ({ item, index }) => {
    //console.log("----------------------");
    //console.log(item.createdAt.toDate().toString());
    //console.log("----------------------");
    //setTotalCost(parseFloat(totalCost) + parseFloat(item.price));
    //setTotalCount(parseFloat(totalCount) + parseFloat(item.count));
    //totalCost = parseFloat(totalCost) + parseFloat(item.price);
    //totalCount = parseFloat(totalCount) + parseFloat(item.count);

    //console.log(totalCost);
    //console.log(totalCount);

    return (
      <View style={styles1.rowFront}>
        <Text>
          {index}. {item.price} {item.amount} {item.count}
          {"\n" + item.createdAt.toDate().toString()}
        </Text>

        <Icon name={"flight-takeoff"} />
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
        <Text style={styles.buttonText}>Click here to create new order..</Text>
      </TouchableOpacity>
      {orders.length === 0 && (
        <Text>Please order some coins in currency: {coin}</Text>
      )}
      {orders && orders.length > 0 && (
        <>
          <Text>Average Value: {averageValue}</Text>
          <SwipeListView
            data={orders}
            keyExtractor={(item) => item.id}
            renderItem={renderOrder}
            removeClippedSubviews={true}
          />
        </>
      )}
    </View>
  );
}

const styles1 = StyleSheet.create({
  rowFront: {
    alignItems: "center",
    backgroundColor: "#FFF",
    borderBottomWidth: 0.25,
    justifyContent: "center",
    height: 50,
  },
  rowBack: {
    alignItems: "center",
    backgroundColor: "#DDD",
    flex: 1,
    flexDirection: "row",
    justifyContent: "space-between",
    paddingLeft: 15,
  },
  backRightBtn: {
    alignItems: "center",
    bottom: 0,
    justifyContent: "center",
    position: "absolute",
    top: 0,
    width: 75,
    backgroundColor: "red",
    right: 0,
  },
});

Upvotes: 0

Views: 709

Answers (1)

jnpdx
jnpdx

Reputation: 52575

You're calling useState hook functions (setTotalCost and setTotalCount) inside your render function. This will end up causing issues like the error that you're seeing or even worse, an infinite loop.

Instead of calling these when rendering, use useEffect instead and perform the necessarily calculations whenever the related dependencies change. It looks like you're already doing that for setAverageValue, so you're familiar with what's involved in doing that.

Update, based on comments:

useEffect(() => {
 //calculate and set anything like totalCost, averageValue, etc here 
}, [orders])

Upvotes: 1

Related Questions