fwara07
fwara07

Reputation: 45

.map function not working with React Native

I have a .map() function with JSX code inside. Although, the JSX is not rendering. It is only rendering after I save the file. I am using expo (React Native).

Here is my code:

import React, { useEffect, useState } from "react";
import * as SecureStore from "expo-secure-store";
import { View, Text, ActivityIndicator } from "react-native";
import { Button } from "react-native-elements";

const Receipts = ({ navigation }) => {
  const [receipts, setReceipts] = useState([]);
  const [loading, setLoding] = useState(true);
  const [result, setResult] = useState({});
  const [keys, setKeys] = useState([]);
  useEffect(() => {
    const getReceiptsData = async () => {
      let token = await SecureStore.getItemAsync("token");
      console.log(token);
      fetch("https://notrealapi/api/receipts", {
        method: "GET",
        headers: {
          Authorization: `JWT ${JSON.parse(token)}`,
        },
      })
        .then((res) => res.json())
        .then((json) => {
          setReceipts(json);
          setLoding(false);
        })
        .catch((error) => console.error(error));
    };

    getReceiptsData();
    processReceipts();
  }, []);

  const processReceipts = () => {
    const dubps = [];
    const resultObj = {};
    receipts.map((item) => {
      if (dubps.includes(item.merchant_name)) {
        resultObj[item.merchant_name] =
          resultObj[item.merchant_name] + parseFloat(item.total);
      } else {
        resultObj[item.merchant_name] = parseFloat(item.total);
        dubps.push(item.merchant_name);
      }
    });
    setResult(resultObj);
    setKeys(Object.keys(resultObj));
  };

  const exportReport = async () => {
    let token = await SecureStore.getItemAsync("token");
    fetch("https://notrealapi/api/export", {
      method: "GET",
      headers: {
        Authorization: `JWT ${JSON.parse(token)}`,
      },
    })
      .then((res) => res.json())
      .then((json) => {
        console.log(json);
      })
      .catch((error) => console.error(error));
  };

  const renderSummary = () => {
    return keys.map((key) => {
      return (
        <View>
          <Text
            key={key}
            style={{
              fontSize: 15,
              fontWeight: "normal",
              paddingBottom: 50,
            }}
          >
            {`You have spent $${result[key].toString()} at ${key}`}
          </Text>
        </View>
      );
    });
  };

  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
      {loading ? (
        <ActivityIndicator size="large" color="blue" />
      ) : (
        <>
          <Text style={{ fontSize: 30, fontWeight: "bold", paddingBottom: 50 }}>
            Summary:
          </Text>
          {renderSummary()}
          <Button
            type="outline"
            title="Export detailed report"
            onPress={exportReport}
          />
          <Text style={{ fontSize: 10, marginTop: 10 }}>
            *The detailed report shall be sent by email.
          </Text>
        </>
      )}
    </View>
  );
};

export default Receipts;

Note: It does work but only when I save the file and it refreshes using expo CLI. Also, error occurs in the renderSummary() function.

Update: keys can be equal to ["Costco"] and result can be equal to {Costco: 69.99}

Upvotes: 1

Views: 329

Answers (1)

ksav
ksav

Reputation: 20821

You are running processReceipts() before the fetch within getReceiptsData() has resolved.

Notice the order of the console logs in this example.

import React, { useEffect, useState } from "react";

const Receipts = () => {
  const [receipts, setReceipts] = useState([]);
  const [loading, setLoding] = useState(true);
  const [result, setResult] = useState({});
  const [keys, setKeys] = useState([]);

  useEffect(() => {
    const getReceiptsData = async () => {
      fetch("https://rickandmortyapi.com/api/character/1", {
        method: "GET"
      })
        .then((res) => res.json())
        .then((json) => {
          console.log("getReceiptsData resolves");
          setReceipts(json);
          setLoding(false);
        })
        .catch((error) => console.error(error));
    };

    getReceiptsData(); // js won't wait here
    processReceipts();
  }, []);

  const processReceipts = (json) => {
    console.log("processReceipts()");
  };
  return null;
};

export default Receipts;

Edit processReceipts-executes-too-early-early


Instead, handle the data manipulation when the fetch resolves.

import React, { useEffect, useState } from "react";

const Receipts = () => {
  const [loading, setLoding] = useState(true);
  const [result, setResult] = useState({});

  useEffect(() => {
    const getReceiptsData = async () => {
      fetch("https://rickandmortyapi.com/api/character/1", {
        method: "GET"
      })
        .then((res) => res.json())
        .then((json) => {
          console.log("getReceiptsData resolves");
          processReceipts(json);
          setLoding(false);
        })
        .catch((error) => console.error(error));
    };

    getReceiptsData();
  }, []);

  const processReceipts = (json) => {
    console.log("processReceipts()");
    // do some work and then setResult
  };
  return null;
};

export default Receipts;

Edit processReceipts-executes-too-early-early (forked)


Also, avoid storing a state that is derived from another state where possible. You should either translate the server payload into usable data:

  • when you receive the payload then set it to state OR
  • when you are rendering

Upvotes: 1

Related Questions