React - mapping async data to a component - javascript

I have an API get request here, being called on mount in my useEffect hook and then set
const [multilineAccountInfo, setMultilineAccountInfo] = useState([]);
useEffect(() => {
getMultilineAccountInfo();
return () => {
console.log('cleanup home/balance');
};
}, []);
const getMultilineAccountInfo = () => {
axios
.get('Account/MultiLineAllAccountsInfo')
.then(response => {
let parse_response = JSON.parse(response.data);
let stringified_data = JSON.stringify(parse_response);
setMultilineAccountInfo(stringified_data);
})
.catch(error => {
console.log('getMultilineAccountInfo error', error);
});
};
The problem that I'm facing is that in my View here, the app crashes and says "undefined is not a function (near '...multilineAccountInfo.map...')"
const MultilineDetailHome = multilineAccountInfo.map((item, index) => (
<View key={index}>
<View style={styles.multilineCards}>
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}>
<View>
<Text style={styles.multilineNameText}>
{item.FirstName + ' ' + item.LastName}
</Text>
<Text style={styles.multilinePhoneText}>
{formatPhoneNumber(item.Pnum)}
</Text>
</View>
<View style={{marginLeft: hp('3%')}}>
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text style={styles.multilineBalanceText}>
${item.Balance.toFixed(2)}
</Text>
</View>
<Text style={styles.multilineTypeText}>{item.AccountType}</Text>
</View>
</View>
</View>
</View>
));
I am pretty sure this is happening because of the async nature of setState() and the data not being returned in time before the .map() method is invoked.
How would I go about waiting for the API call to finish before the .map() and finally rendering the View in the UI?
Thanks for any help!

If you have problem while loading data, you can use this:
multilineAccountInfo.length > 0 ? YOUR COMPONENT : <h1>LOADING</h1>
another way can be using another state like:
[isLoading, setLoading] = useState(true);
and then setting it after your async method.
.then(response => {
let parse_response = JSON.parse(response.data);
let stringified_data = JSON.stringify(parse_response);
setMultilineAccountInfo(stringified_data);
setLoading(false);
})
now You can use it in your component rendering section:
isLoading? Text("Loading"):YOUR COMPONENT

Related

Retrieve data from a stored object

I use MMKV dependency to store and retrieve data put in Storage. In this case I want to store the data of a user. But I have a problem when I want to retrieve this data as an object. Indeed, when I console.log my hook currentUserData I have all the data like this:
{"user":{"id":9,"email":"userEmail","first_name":"firstNameUser","last_name":"","zipcode":"","city":"userCity","address":""},"token":"tokenUser"}
But when I console.log(currenUserData.user) or console.log(currentUserData.user.email) for example it puts me undefined, I don't understand why.
Here is my code :
import {MMKV, useMMKVObject, useMMKVString} from 'react-native-mmkv';
const storage = new MMKV();
export default function HomeLogin() {
const [loading, setLoading] = useState(false);
const [currentData, setCurrentData] = useState({});
const [currentUserData, setCurrentUserData] = useState({});
const navigation = useNavigation();
useEffect(() => {
const jsonValue = storage.getString('user');
const parsedUserData = JSON.parse(jsonValue) || {};
setCurrentUserData(parsedUserData);
}, [currentUserData]);
/* I have tried this too but the same result : error currentDataUser.user.city and currentData.user.first_name is undefined
useEffect(() => {
setLoading(true);
const getUserData = async () => {
try {
/!*const jsonValue = storage.getString('user');
const parsedUserData = JSON.parse(jsonValue) || {};
setCurrentUserData(parsedUserData);*!/
const response = await axios.get(
`https://api.weatherapi.com/v1/current.json?key=${APIKEY}&q=${parsedUserData?.user?.city}&aqi=no&lang=fr`,
);
setCurrentData(response.data.current.condition);
setLoading(false);
} catch (e) {
setLoading(false);
alert(e);
}
};
getUserData();
}, []);*/
if (loading) {
return (
<View style={{flex: 1, justifyContent: 'center'}}>
<ActivityIndicator size="large" color="#3c693d" />
</View>
);
} else {
return (
<View style={styles.container}>
<View style={styles.content}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 50,
}}>
<View
style={{flexDirection: 'column', marginLeft: 20, marginTop: 20}}>
<Image
style={{width: 50, height: 50}}
source={{
uri: `https:${
Object.keys(currentUserData).length > 0
? currentData.icon
: ''
}`,
}}
/>
</View>
<Text style={styles.title}>
Bienvenue, {'\n'}{' '}
<Text style={{fontWeight: 'bold'}}>
{Object.keys(currentUserData).length > 0
? currentUserData.user.first_name
: ''}
</Text>
</Text>
</View>
</View>
);
}
}
Thanks for help !

Problem of state undefined in useEffect function

I have a problem with my useEffect, I think it's due to the asynchronous functions inside but I don't know how to fix the problem. Let me explain: in my useEffect, I have a function that is used to retrieve user data thanks to the AsyncStorage and I want that in a weather API request I can enter the user's city as a parameter so it works on the spot but when I reload the application it gives me an error message like : currentUserData.user.city is undefined
Here is the code :
const [loading, setLoading] = useState(false);
const [currentData, setCurrentData] = useState({});
const [currentUserData, setCurrentUserData] = useState({});
const navigation = useNavigation();
useEffect(() => {
setLoading(true);
const getUserData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('user');
setCurrentUserData(JSON.parse(jsonValue));
const response = await axios.get(
`https://api.weatherapi.com/v1/current.json?key=${APIKEY}&q=${currentUserData.user.city}&aqi=no&lang=fr`,
);
setCurrentData(response.data.current.condition);
setLoading(false);
} catch (e) {
setLoading(false);
alert(e);
}
};
getUserData();
}, []);
if (loading) {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text
style={{
fontSize: 80,
color: 'red',
}}>
Loading...
</Text>
</View>
);
} else {
return (
<View style={styles.container}>
<View style={styles.content}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 50,
}}>
<View
style={{flexDirection: 'column', marginLeft: 20, marginTop: 20}}>
<Image
style={{width: 50, height: 50}}
source={{
uri: `https:${
Object.keys(currentUserData).length > 0
? currentData.icon
: ''
}`,
}}
/>
</View>
<Text style={styles.title}>
Bienvenue, {'\n'}{' '}
<Text style={{fontWeight: 'bold'}}>
{Object.keys(currentUserData).length > 0
? currentUserData.user.first_name
: ''}
</Text>
</Text>
<TouchableWithoutFeedback
onPress={() => navigation.navigate('UserScreen')}>
<FontAwesomeIcon
style={{marginRight: 20, marginTop: 20}}
size={35}
icon={faUser}
/>
</TouchableWithoutFeedback>
</View>
</View>
</View>
);
}
}
Please use ? operator to access the currentUserData.user. e.g.currentUserData?.user?.city.
Because initial state is {}, so currentUserData.user is undefined and your code tried to get value from undefined.
And use in your API URL.
First please write a console. And try to use the following code.
`https://api.weatherapi.com/v1/current.json?key=${APIKEY}&q=${currentUserData? currentUserData?.user?.city || '' : ''}&aqi=no&lang=fr`
Your mistake is that you used the state variable as soon as calling setCurrentUserData. We can't use it in this way. Updated hook is following:
useEffect(() => {
setLoading(true);
const getUserData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('user');
const parsedUserData = JSON.parse(jsonValue) || {};
setCurrentUserData(parsedUserData);
const response = await axios.get(
`https://api.weatherapi.com/v1/current.json?key=${APIKEY}&q=${parsedUserData?.user?.city}&aqi=no&lang=fr`,
);
setCurrentData(response.data.current.condition);
setLoading(false);
} catch (e) {
setLoading(false);
alert(e);
}
};
getUserData();
}, []);
I think you might misuse of React Hook, setCurrentUserData update the state asynchronously, immediately use currentUserData.user.city is completely wrong, thus you either use option below:
Use data returned from AsyncStorage
const jsonValue = await AsyncStorage.getItem('user');
const userData = JSON.parse(jsonValue)
setCurrentUserData(userData);
const response = await axios.get(
`https://api.weatherapi.com/v1/current.json?key=${APIKEY}&q=${currentUserData.user.city}&aqi=no&lang=fr`,
);
Remove logic all below setCurrentUserData(JSON.parse(jsonValue));, and move them into another useEffect with currentUserData as dependency
useEffect(() => {
if(currentUserData) {
// make api request here
const response = await axios.get(
`https://api.weatherapi.com/v1/current.json?key=${APIKEY}&q=${currentUserData.user.city}&aqi=no&lang=fr`,
);
// some logic here...
}
}, [currentUserData])

How to save route.params with asyncstorage?

Srry if the title makes no sense. Don't know a better title.
How can I save route.params items that I pass to my second screen using AsyncStorage?
In my first screen i have a bunch of data in a FlatList that can be opened with a Modal. Inside that Modal I have a TouchableOpacity that can send the data thats inside the Modal to my second screen. The data that has been passed to the second screen is passed to a FlatList. The data in the FlatList should be saved to AsyncStorage. Tried alot of things getting this to work, but only getting warning message
undefined. Code below is the most recent progress.
Using React Navigation V5.
FIRST SCREEN
const [masterDataSource, setMasterDataSource] = useState(DataBase);
const [details, setDetails] = useState('');
<TouchableOpacity
onPress={() => {
const updated = [...masterDataSource];
updated.find(
(item) => item.id === details.id,
).selected = true;
setMasterDataSource(updated);
navigation.navigate('cart', {
screen: 'cart',
params: {
items: updated.filter((item) => item.selected),
},
});
setModalVisible(false);
}}>
<Text>Add to cart</Text>
</TouchableOpacity>
SECOND SCREEN
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity } from 'react-native';
import { useTheme } from '../Data/ThemeContext';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Ionicons from 'react-native-vector-icons/Ionicons';
export default function ShoppingList({ route, navigation }) {
const [shoppingList, setShoppingList] = useState([]);
const { colors } = useTheme();
const todo = () => {
alert('Todo');
};
useEffect(() => {
restoreShoppingListAsync();
}, []);
const shoppingListAsync = () => {
const shoppingList = route.params && route.params.items;
setShoppingList(list);
storeShoppingList(list);
};
const asyncStorageKey = '#ShoppingList';
const storeShoppingListAsync = (list) => {
const stringifiedList = JSON.stringify(list);
AsyncStorage.setItem(asyncStorageKey, stringifiedList).catch((err) => {
console.warn(err);
});
};
const restoreShoppingListAsync = () => {
AsyncStorage.getItem(asyncStorageKey)
.then((stringifiedList) => {
console.log(stringifiedList);
const parsedShoppingList = JSON.parse(stringifiedList);
if (!parsedShoppingList || typeof parsedShoppingList !== 'object')
return;
setShoppingList(parsedShoppingList);
})
.then((err) => {
console.warn(err);
});
};
const RenderItem = ({ item }) => {
return (
<View>
<TouchableOpacity
style={{
marginLeft: 20,
marginRight: 20,
elevation: 3,
backgroundColor: colors.card,
borderRadius: 10,
}}>
<View style={{ margin: 10 }}>
<Text style={{ color: colors.text, fontWeight: '700' }}>
{item.name}
</Text>
<Text style={{ color: colors.text }}>{item.gluten}</Text>
<Text style={{ color: colors.text }}>{item.id}</Text>
</View>
</TouchableOpacity>
</View>
);
};
const emptyComponent = () => {
return (
<View style={{ alignItems: 'center' }}>
<Text style={{ color: colors.text }}>Listan är tom</Text>
</View>
);
};
const itemSeparatorComponent = () => {
return (
<View
style={{
margin: 3,
}}></View>
);
};
return (
<View
style={{
flex: 1,
}}>
<View
style={{
padding: 30,
backgroundColor: colors.Textinput,
elevation: 12,
}}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Ionicons name="arrow-back-outline" size={25} color="#fff" />
</TouchableOpacity>
<Text style={{ color: '#fff', fontSize: 20 }}>Inköpslista</Text>
<TouchableOpacity>
<Ionicons
name="trash-outline"
size={25}
color="#fff"
onPress={() => todo()}
/>
</TouchableOpacity>
</View>
</View>
<View style={{ flex: 1, marginTop: 30 }}>
<FlatList
data={shoppingList}
renderItem={RenderItem}
ListEmptyComponent={emptyComponent}
ItemSeparatorComponent={itemSeparatorComponent}
initialNumToRender={4}
maxToRenderPerBatch={5}
windowSize={10}
removeClippedSubviews={true}
updateCellsBatchingPeriod={100}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ paddingBottom: 20 }}
/>
</View>
</View>
);
}
As you are using async storage to maintain the cart.
I would suggest an approach as below
Update the asyn storage when new items are added to or removed from the cart
Retrieve the items from the cart screen and show the items there
Before you navigate store the items like below
AsyncStorage.setItem(
'Items',
JSON.stringify(updated.filter((item) => item.selected))
).then(() => {
navigation.navigate('Cart', {
items: updated.filter((item) => item.selected),
});
});
The cart screen would be something like below
function Cart({ navigation, route }) {
const [data,setData]=useState([]);
React.useEffect(() => {
async function fetchMyAPI() {
const value = await AsyncStorage.getItem('Items');
setData(JSON.parse(value));
}
fetchMyAPI();
}, []);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Go back" onPress={() => navigation.goBack()} />
<FlatList
data={data}
renderItem={RenderItem}
/>
</View>
);
}
Working Example
https://snack.expo.io/#guruparan/cartexample

FlatList does not show the API result

I'm just training how to consume apis in react-native with useEffects and in console.log () returns the results, but in the view it doesn't show, I think it's the keyExtractor or not ...
const api = 'https://randomuser.me/api/?&results=2';
const Detalhes = () => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
setIsLoading(true);
fetch(api)
.then((response) => response.json())
.then((results) => {
setData(results);
setIsLoading(false);
console.log(results);
})
.catch((err) => {
setIsLoading(false);
setError(err);
});
}, []);
if (isLoading) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large" color="#5500dc" />
</View>
);
}
if (error) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 18}}>
Error...
</Text>
</View>
);
}
and this is the code with FlatList that should be returned in View and as said, I can see the result in console.log (react native debug)
<SafeAreaView>
<FlatList
data={data}
keyExtractor={item => item.first}
renderItem={({item}) => (
<View style={styles.metaInfo}>
<Text style={styles.text}>
{`${item.name.first} ${item.name.last}`}
</Text>
</View>
)}
/>
</SafeAreaView>
Try to see again console.log(results). Api response is an object { results, info }, not an array. The results property of results is array.
Replace setData(results); by setData(results.results); to resolve your problem.
Sorry for my bad English.
You are giving name in your keyExtractor which could be same repeat so It won't work. Try changing keyExtractor like below to provide unique index for each item
keyExtractor={(item,index)=>index.toString()}

React Naitve shows only one View

in my app on my homescreen I am rendering a ListList with my items. Now I wanted to add a little info text above it. But it only shows my text and skips(?) my FlatList. Could anyone help me and explain me this behaviour?
If I have my text component in my file it only shows the text. If I only use FlatList it shows correctly my list with my data. But if I try to combine both, it only shows one component. Same thing when I use only FlatList and wrap it inside of View then I get only a white blank screen.
const JobsScreen = (props) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const allJobs = useSelector((state) => state.job.availableJobs);
const loadJobs = useCallback(async () => {
setIsRefreshing(true);
try {
await dispatch(jobActions.fetchAllJobs());
} catch (err) {}
setIsRefreshing(false);
}, [dispatch]);
useEffect(() => {
setIsLoading(true);
loadJobs().then(() => {
setIsLoading(false);
});
}, [dispatch, loadJobs]);
useEffect(() => {
const willFocusSub = props.navigation.addListener("willFocus", loadJobs);
return () => {
willFocusSub.remove();
};
}, [dispatch, loadJobs]);
if (isLoading) {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#2f3640",
}}
>
<ActivityIndicator size="large" color="#fff" animating />
</View>
);
}
return (
<View>
<FlatList
data={allJobs}
onRefresh={loadJobs}
refreshing={isRefreshing}
keyExtractor={(item) => item.id}
style={{ flex: 1, backgroundColor: "#1a191e" }}
renderItem={(itemData) => (
<JobItem
description={itemData.item.description}
titel={itemData.item.titel}
fname={itemData.item.fname}
cover={itemData.item.cover}
genre={itemData.item.genre}
year={itemData.item.year}
id={itemData.item.id}
// friend={itemData.item.friend}
/>
)}
/>
</View>
);
};
Got it by myself.
<View style={{ height: "100%" }}>
solved it.
Try this.
<View style={{ flex: 1 }}>

Categories

Resources