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}
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;
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;
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
Related
i've a component that i import, but its not displayed on the page.
this is my app.js file. i imported the <LineGraph/>component but it is not getting displayed properly on the browser.
import React, { useEffect, useState } from "react";
import {
MenuItem,
FormControl,
Select,
Card,
CardContent,
} from "#material-ui/core";
import InfoBox from "./infoBox";
import Table from "./table";
import "./App.css";
import { sortData } from "./util";
import LineGraph from "./LineGraph";
const App = () =\> {
const \[countries, setCountries\] = useState(\[\]);
const \[country, setCountry\] = useState("worldwide");
const \[countryInfo, setCountryInfo\] = useState({});
const \[tableData, setTableData\] = useState(\[\]);
const \[casesType, setCasesType\] = useState("cases");
useEffect(() =\> {
fetch("https://disease.sh/v3/covid-19/all")
.then((response) =\> response.json())
.then((data) =\> {
setCountryInfo(data);
});
}, \[\]);
useEffect(() =\> {
const getCountriesData = async () =\> {
fetch("https://disease.sh/v3/covid-19/countries")
.then((response) =\> response.json())
.then((data) =\> {
const countries = data.map((country) =\> ({
name: country.country,
value: country.countryInfo.iso2,
}));
const sortedData = sortData(data);
setTableData(sortedData);
setCountries(countries);
});
};
getCountriesData();
}, \[\]);
const onCountryChange = async (event) =\> {
const countryCode = event.target.value;
console.log("s", countryCode);
setCountry(countryCode);
const url =
countryCode === "worldwide"
? "https://disease.sh/v3/covid-19/all"
: `https://disease.sh/v3/covid-19/countries/${countryCode}`;
await fetch(url)
.then((response) => response.json())
.then((data) => {
setCountry(countryCode);
setCountryInfo(data);
});
};
console.log("CuntryInfo: ", countryInfo);
return (
\<div className="App"\>
\<div className="app__left"\>
\<div className="app__header"\>
\<h1\>COVID-19 Tracker\</h1\>
\<FormControl className="app__dropdown"\>
\<Select
variant="outlined"
onChange={onCountryChange}
value={country}
\\>
\<MenuItem value="worldwide"\>Worldwide\</MenuItem\>
{countries.map((country) =\> (
\<MenuItem value={country.value}\>{country.name}\</MenuItem\>
))}
\</Select\>
\</FormControl\>
\</div\>
<div className="app__stats">
<InfoBox
title="Coronavirus cases"
cases={countryInfo.todayCases}
total={countryInfo.cases}
/>
<InfoBox
title="Recovered"
cases={countryInfo.todayRecovered}
total={countryInfo.recovered}
/>
<InfoBox
title="Deaths"
cases={countryInfo.todayDeaths}
total={countryInfo.deaths}
/>
</div>
</div>
<Card className="app__right">
<CardContent>
{/* Table */}
<h3>Live Cases by country</h3>
<Table countries={tableData} />
{/* Graph */}
<h3>Word Wide New </h3>
<LineGraph casesType={casesType} />
</CardContent>
</Card>
</div>
);
};
export default App;
and My content of LineGraph.js :
import React, { useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
import numeral from "numeral";
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 0,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,0");
},
},
},
};
const buildChartData = (data, casesType) => {
let chartData = [];
let lastDataPoint;
for (let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint,
};
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async () => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
console.log(chartData);
// buildChart(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data,
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
When I import the LineGraph.js component in the App.js file, the output is not displayed without any error.
in console error is :
react-dom.development.js:25830
Uncaught Error: "category" is not a registered scale.
I am using react-native for my spotify api app and when I want to get data from my server with axios (in useEffect because I want to render the returned items when the component loads) it throws error: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. So if anyone knows how to solve this problem I would be very greatful.
import React, {useEffect, useState} from 'react'
import { View, Text, StyleSheet, Image, FlatList, ActivityIndicator} from 'react-native'
import axios from 'axios';
import AsyncStorage from '#react-native-async-storage/async-storage';
export default function ArtistsCom({route}) {
const type = route.name.toLowerCase();
const [time, setTime] = useState('short_term');
const [access_token, setAccess_token] = useState('');
const [refresh_token, setRefresh_token] = useState('');
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
AsyncStorage.getItem('refresh_token')
.then(value => setRefresh_token(value));
const getDataAsync = async () => {
const res = await axios.post(`http://localhost:3001/api/refresh`, {
headers: {
body: {
refresh_token: refresh_token
}
}
})
console.log(res);
setAccess_token(res.data.access_token);
return res;
};
getDataAsync();
}, []);
return (
<View>
<View>
{!loading ? items.map((item, key) => {
return (
<View key={key} style={{width: 150, height: 150, margin: 10}}>
<Image style={{width: '100%', height: '100%'}} source={{uri: type == 'artists' ? item.images[0].url : item.album.images[0].url}} />
<View style={{position: 'absolute', bottom: 0, left:4, height: 20, width: '100%', backgroudColor: 'red'}}><Text style={{color: 'white'}}>{key+1}. {item.name}</Text></View>
</View>
)
}) : <Text>Loading...</Text>}
</View>
</View>
)
}
You can use a cancel token to cancel in-flight requests.
useEffect(() => {
...
// Create cancel token and source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getDataAsync = async () => {
const res = await axios.post(
`http://localhost:3001/api/refresh`,
{
cancelToken: source.token, // <-- attach cancel token to request
headers: {
body: {
refresh_token: refresh_token
}
}
},
);
console.log(res);
setItems(res.data.items);
return res;
};
getDataAsync();
// Return useEffect cleanup function to cancel request
return () => {
source.cancel('Component unmounted'); // message is optional
};
}, []);
Update
Enclose the asynchronous logic in a try/catch/finally to make the multiple requests and handle any rejected promises and errors.
useEffect(() => {
// Create cancel token and source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getDataAsync = async () => {
setLoading(true);
try {
const refresh_token = await AsyncStorage.getItem('refresh_token');
const res = await axios.post(
`http://localhost:3001/api/refresh`,
{
cancelToken: source.token, // <-- attach cancel token to request
headers: {
body: {
refresh_token
}
}
},
);
const { access_token } = res.data;
setAccess_token(access_token);
const res2 = await axios.get(`http://localhost:3001/api/top/${type}?time_range=${time}&limit=50&access_token=${access_token}`);
setItems(res2.data.items);
} catch(error) {
// handle any errors, log them, set some state, etc...
} finally {
setLoading(false);
}
};
getDataAsync();
// Return useEffect cleanup function to cancel request
return () => {
source.cancel('Component unmounted'); // message is optional
};
}, [refreshToken]);
I figured it all
Here is the code:
const type = route.name.toLowerCase();
const [time, setTime] = useState('short_term');
const [access_token, setAccess_token] = useState('');
const [refresh_token, setRefresh_token] = useState('');
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const getItems = (ACCESS_TOKEN) => {
axios.get(`http://localhost:3001/api/top/${type}?time_range=${time}&limit=50&access_token=${ACCESS_TOKEN}`)
.then(res => {
setItems(res.data.items);
setLoading(false);
})
}
useEffect(() => {
setLoading(true);
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getRefreshedAccessToken = async() => {
axios.post(`http://localhost:3001/api/refresh`, {
cancelToken: source.token,
headers: {
body: {
refresh_token: await AsyncStorage.getItem('refresh_token')
}
}
})
.then(res => {
console.log(res.data.access_token);
setAccess_token(res.data.access_token);
getItems(res.data.access_token);
})
.catch(err => console.log(err))
};
getRefreshedAccessToken();
return () => {
source.cancel('Component unmounted');
}
}, [refresh_token]);
I have a REACT NATIVE app and in a component I call with AXIOS an API to get rows from a DATABASE.
I have loadData function that execute the call to the DB and set some info in the STATE to monitor the execution and to save the results.
I call this loadData in useEffect but when I change screen I receive Can't perform a React state update on an unmounted component
This is my component:
const HomeScreen = props => {
//I take the id from the state
const Id = useSelector(state => state.auth.Id);
const [data, setData] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
//Manage the duration of the http request
const [executionDuration, setExecutionDuration] = useState(0);
const [deleteExecutionCounting, setDeleteExecutionCounting] = useState(0);
const executionDurationRef = useRef(executionDuration);
executionDurationRef.current = executionDuration;
const deleteExecutionCountingRef = useRef(deleteExecutionCounting);
deleteExecutionCountingRef.current = deleteExecutionCounting;
//call the api
//the call to the api has made manually
const loadData = useCallback(() => {
setDeleteExecutionCounting(setInterval((() => {
setExecutionDuration(executionDurationRef.current + 1);
}), 2000));
setError(false);
setLoading(true);
getRowsFromDb(Id).then(function (response) {
if (response.status === 204) {
setData({ rows: [], count: 0 });
}
else {
setData(response.data);
}
})
.catch(function (error) {
setError(true)
})
.then(function () {
// always executed
clearInterval(deleteExecutionCountingRef.current);
setExecutionDuration(0);
setDeleteExecutionCounting(0);
setLoading(false);
});
}, [Id]);
//this is used to refresh the data every time the screen is visible
useEffect(() => {
const unsubscribe = props.navigation.addListener('focus', () => {
// The screen is focused
loadData();
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [Id, props.navigation, loadData]);
useEffect(() => {
loadData();
}, [Id, props.navigation, loadData]);
//check Loading state
if (loading)
return (
<View style={styles.screenMessage}>
<ActivityIndicator size='large' />
{executionDurationRef.current > 3 &&
(
<Text>We're taking too long</Text>
)}
{executionDurationRef.current > 6 &&
(<View>
<Text>there is something wrong</Text>
</View>
)}
</View>
)
//check error state
if (error) {
console.log(errorOrders);
return (
<View style={styles.screenMessage}>
<Error onPress={() => { loadData(); }}>Try again</Error>
</View>
)
}
//This is made for the first rendering to avoid to reach the code down when the value should be initialized
if ((!loading && data === undefined)) {
return null;
}
return (
//normal rendering of a component
);
};
const styles = StyleSheet.create({
screenMessage: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: Colors.bodyBackgroundColor
},
bodyContainer: {
flex: 1,
justifyContent: "center",
backgroundColor: Colors.bodyBackgroundColor
},
});
export default HomeScreen;
How can I solve this?
I am new to React native and trying to destructure the json response which i have got from my API call using the map function and somehow it is giving me the above error. I want to display the aqi and dominant pollutants using a text componenet. I am using the AQICN API.
import React,{ useState , useEffect} from 'react';
import { StyleSheet, Text, View ,ActivityIndicator, ScrollView,FlatList} from 'react-native';
import * as Location from 'expo-location';
export default function HomeScreen({navigation}) {
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
//Lat and Long
const [latitude, setLatitude] = useState(null);
const [longitude , setLongitude]= useState(null);
const [data, setData] = useState([]);
const [loader, setLoader]=useState(true);
useEffect(() => {
(
async () => {
let { status } = await Location.requestPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission Denied');
return;
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
//Changes
setLatitude(location.coords.latitude);
setLongitude(location.coords.longitude);
const la=latitude;
const lo=longitude;
async function AqicnApiCall() {
let res = await fetch("https://api.waqi.info/feed/geo:"+ latitude +";"+ longitude +"/?token=ac3a71fc80931abd95ede14c2040f0678f578703")
.then((response) => response.json())
.then((json) => setData(json.data))
.catch((error) =>console.log(error))
}
AqicnApiCall();
})();
}, [latitude, longitude]);
//const obj=JSON.stringify(data);
return (
<ScrollView style={styles.container}>
{
data.map((d) =>{
console.log(d);
return(
<Text style={styles.container}>{d.data.aqi}</Text>
)
})
}
</ScrollView>
);
}
const styles= StyleSheet.create({
container: {
padding:20,
marginTop:15,
margin:10,
},
paragraph : {
padding:20,
marginTop:5,
}
});
This is the API response, i need the dominant pollutant and aqi.
Working App: Expo Snack
Access aqi and dominentpol like below:
return (
<ScrollView style={styles.container}>
<Text style={styles.container}>AQI: {data?.aqi}</Text>
<Text style={styles.container}>
Dominant Pollutant: {data?.dominentpol}
</Text>
</ScrollView>
);
}
Full Working code:
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
ActivityIndicator,
ScrollView,
FlatList,
} from 'react-native';
import * as Location from 'expo-location';
export default function HomeScreen({ navigation }) {
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
//Lat and Long
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const [data, setData] = useState([]);
const [loader, setLoader] = useState(true);
useEffect(() => {
(async () => {
let { status } = await Location.requestPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission Denied');
return;
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
//Changes
setLatitude(location.coords.latitude);
setLongitude(location.coords.longitude);
const la = latitude;
const lo = longitude;
async function AqicnApiCall() {
let res = await fetch(
'https://api.waqi.info/feed/geo:' +
latitude +
';' +
longitude +
'/?token=ac3a71fc80931abd95ede14c2040f0678f578703'
)
.then((response) => response.json())
.then((json) => {
console.log('data: ', json.data);
setData(json.data);
})
.catch((error) => console.log(error));
}
AqicnApiCall();
})();
}, [latitude, longitude]);
//const obj=JSON.stringify(data);
return (
<ScrollView style={styles.container}>
<Text style={styles.container}>AQI: {data?.aqi}</Text>
<Text style={styles.container}>
Dominant Pollutant: {data?.dominentpol}
</Text>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
marginTop: 15,
margin: 10,
},
paragraph: {
padding: 20,
marginTop: 5,
},
});
As data isn't an array, and I see that you just want to display aqi value,
<ScrollView style={styles.container}>
<Text style={styles.container}>{data?.aqi}</Text>
</ScrollView>
On the screenshot, it seems that you can only iterate on data.attributions.
<ScrollView style={styles.container}>
{data.attributions.map(attr => <Text>{attr.something}</Text>}
</ScrollView>
I think some of d.data is undefined or null. Please try this.
<ScrollView style={styles.container}>
{
data && data.map((d) =>{
console.log(d);
return(
<Text style={styles.container}>{d?.data?.aqi}</Text>
)
})
}
</ScrollView>
I am displaying a list of contacts from my address book in a flatlist and want to be able to search the list. The issue is that initially the list is empty and I get an error because it is trying to filter undefined.
If I type a name though it works and if I delete my search query it then shows all users. I would like it do this from the start. I am not sure why it is undefined intially, perhaps because the state has not yet been set.
const AddContactScreen = ({ navigation }) => {
const [contacts, setContacts] = useState();
const [query, setQuery] = useState("");
const [filteredContactList, setFilteredContactList] = useState(contacts);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
sort: Contacts.SortTypes.FirstName,
});
if (data.length > 0) {
setContacts(data);
}
const newContacts = contacts.filter((item) =>
item.name.includes(query)
);
setFilteredContactList(newContacts);
}
})();
}, [query]);
return (
<Screen>
<FlatList
ListHeaderComponent={
<View style={styles.searchContainer}>
<TextInput
style={styles.searchField}
placeholder="Search"
onChangeText={setQuery}
value={query}
/>
</View>
}
data={filteredContactList}
ItemSeparatorComponent={ListItemSeparator}
keyExtractor={(contact) => contact.id.toString()}
renderItem={({ item }) => (
<ListItem
title={item.name}
onPress={() => console.log("contact selected", item)}
/>
)}
/>
</Screen>
);
};
const styles = StyleSheet.create({
searchContainer: {
padding: 15,
},
searchField: {
borderRadius: 15,
borderColor: "gray",
borderWidth: 1,
padding: 10,
},
});
export default AddContactScreen;
The best practice would be to set your initial value of contacts to an empty array []
So your state would be
const [contacts, setContacts] = useState([]);
const [contacts, setContacts] = useState();
const [query, setQuery] = useState("");
const [filteredContactList, setFilteredContactList] = useState(contacts);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
setLoading(true);
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
sort: Contacts.SortTypes.FirstName,
});
setLoading(false);
setContacts(data.length ? data : []);
}
})();
}, [query]);
useEffect(() => {
if(contacts.length) {
const newContacts = contacts.filter((item) =>
item.name.includes(query)
);
setFilteredContactList(newContacts);
}
}, [contacts])
if(loading) return <p>loading...</p>;
if(!contacts.length) return <p>No data found</>;
// your rest of the code.
NOTE: i have written the code from the imagination. You might need to do some tweak.
I would use useEffect when component mounts to get initial contacts and handle the query in a separate function:
useEffect(() => {
getInitialContacts()
}, []);
const getInitialContacts = async () => {
setLoading(true);
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
sort: Contacts.SortTypes.FirstName,
});
setLoading(false);
setContacts(data.length ? data : []);
}
});
}
you can use another function that you call when text is entered:
const handleQuery = (query) => {
const newContacts = contacts.filter((item) =>
item.name.includes(query)
);
setFilteredContactList(newContacts);
}