Problem of state undefined in useEffect function - javascript

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])

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 !

React - mapping async data to a component

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

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 native and useFocusEffect duplicate data when navigating back

I am struggling with useFocusEffect or useEffect. I am trying to fetch data when user navigate to the user profile this is working but when user navigate back to the profile i have a duplicate posts! I've been working on this problem for 3 days. Please any ideas.
This is the code:
import React, { useState, useEffect, useRef } from "react";
import {
View,
Text,
FlatList,
Image,
TouchableOpacity,
Animated,
ActivityIndicator,
} from "react-native";
import APIKIT from "../../../services/APIKit";
import styles from "./UserPostStyle";
import moment from "moment";
import { MaterialCommunityIcons, FontAwesome } from "#expo/vector-icons";
import { useNavigation } from "#react-navigation/native";
import { useFocusEffect } from "#react-navigation/native";
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
moment.updateLocale("en", {
relativeTime: {
future: "in %s",
past: "%s ago",
s: "Just now",
ss: "%ss",
m: "1m",
mm: "%dm",
h: "1h",
hh: "%dh",
d: "1d",
dd: "%dd",
M: "1mo",
MM: "%dmo",
y: "1y",
yy: "%dy",
},
});
export function UserPosts({ ...props }) {
const [userPosts, setUserPosts] = useState([]);
const [posts, setPosts] = useState([]); // Array with the posts objects
let postsArray = useRef(posts).current; // Auxiliar array for storing posts before updating the state. Pd: this will not re-render the component as it is a reference
const [page, setPage] = useState(1);
const [lastPage, setLastPage] = useState(0);
const [loadingMore, setLoadingMore] = useState(false);
const navigation = useNavigation();
useFocusEffect(
React.useCallback(() => {
if (props.userId) {
fetchPosts();
}
return () => {};
}, [props.userToken, props.userId, page])
);
// useEffect(() => {
// console.log(" -----------useEffect page", page);
// console.log(" -----------useEffect lastPage", lastPage);
// let mounted = true;
// if (mounted && props.userId) {
// fetchPosts();
// }
// return () => {
// console.log("return useEffect page", page);
// console.log("return useEffect lastPage", lastPage);
// mounted = false;
// };
// }, [props.userToken, props.userId, page]);
const fetchPosts = async () => {
if (props.userToken !== null) {
const config = {
headers: { Authorization: `Bearer ${props.userToken}` },
};
APIKIT.get(`/user/posts/${props.userId}?page=${page}`, config)
.then((response) => {
let posts = [];
const { data } = response;
setLastPage(data.userPosts.last_page);
for (let userPost of data.userPosts.data) {
let post = {};
post["id"] = userPost.id;
post["location_coordinate"] = userPost.location_coordinate;
post["location_icon"] = userPost.location_icon;
post["location_id"] = userPost.location_id;
post["location_title"] = userPost.location_title;
post["location_vicinity"] = userPost.location_vicinity;
post["post_body"] = userPost.post_body;
post["created_at"] = moment(userPost.created_at).fromNow(true);
post["user_id"] = userPost.user_id;
posts.push(post);
}
posts.forEach((newPost) => {
// Add the new fetched post to the head of the posts list
postsArray.unshift(newPost);
});
setPosts([...postsArray]); // Shallow copy of the posts array to force a FlatList re-render
setUserPosts((prevPosts) => [...prevPosts, ...posts]);
setLoadingMore(false);
if (page === lastPage) {
setLoadingMore(false);
return;
}
})
.catch((e) => {
console.log("There is an error eccured while getting the posts ", e);
setLoadingMore(false);
});
}
};
const Item = ({
post_body,
id,
location_coordinate,
location_icon,
location_title,
location_vicinity,
location_id,
created_at,
}) => (
<View style={styles.postContainer}>
<TouchableOpacity
onPress={() => {
navigation.navigate("PostDetailsScreen", {
location_coordinate: JSON.parse(location_coordinate),
userAvatar: props.userAvatar,
username: props.username,
name: props.name,
created_at: created_at,
post_body: post_body,
location_title: location_title,
location_vicinity: location_vicinity,
location_icon: location_icon,
location_id: location_id,
});
}}
>
<View style={styles.postHeader}>
<Image
style={styles.userAvatar}
source={
props.userAvatar
? { uri: props.userAvatar }
: require("../../../../assets/images/default.jpg")
}
/>
<View style={styles.postUserMeta}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Text style={styles.name}>{props.name}</Text>
<Text style={styles.createdAt}>{created_at}</Text>
</View>
<Text style={styles.username}>#{props.username}</Text>
</View>
</View>
<View style={styles.postContent}>
<View>
<Text style={styles.postBody}>{post_body}</Text>
</View>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.locationInfoContainer}
onPress={() => {
navigation.navigate("PostPlaceDetailsScreen", {
location_coordinate: JSON.parse(location_coordinate),
userAvatar: props.userAvatar,
username: props.username,
name: props.name,
created_at: created_at,
post_body: post_body,
location_title: location_title,
location_vicinity: location_vicinity,
location_icon: location_icon,
location_id: location_id,
});
}}
>
<View style={styles.locationInfo}>
<Image style={styles.locationIcon} source={{ uri: location_icon }} />
<View style={styles.locationMeta}>
<Text style={styles.locationTitle}>{location_title}</Text>
</View>
</View>
</TouchableOpacity>
<View
style={{
borderWidth: 1,
borderColor: "#F2F2F2",
marginTop: 10,
marginBottom: 10,
}}
/>
<View style={styles.postFooter}>
<TouchableOpacity>
<MaterialCommunityIcons name="comment" size={24} color="#999999" />
</TouchableOpacity>
<TouchableOpacity>
<FontAwesome name="star" size={24} color="#999999" />
</TouchableOpacity>
{/* After fav color #FFBE5B */}
<TouchableOpacity>
<MaterialCommunityIcons
name="dots-horizontal"
size={24}
color="#999999"
/>
</TouchableOpacity>
</View>
</View>
);
const renderItem = ({ item }) => (
<Item
location_coordinate={item.location_coordinate}
post_body={item.post_body}
id={item.id}
location_id={item.location_id}
location_icon={item.location_icon}
location_title={item.location_title}
location_vicinity={item.location_vicinity}
created_at={item.created_at}
/>
);
const emptyPosts = () => {
return (
<View style={styles.noPostsMessageContainer}>
<View style={styles.messageContainer}>
<Image
style={styles.noPostMessageImage}
source={require("../../../../assets/images/Logo.png")}
/>
<Text style={styles.noPostMessageText}>No posts yet!</Text>
</View>
</View>
);
};
const handleLoadingMore = () => {
if (page === lastPage) {
return;
}
setPage(page + 1);
setLoadingMore(true);
};
return (
<View style={styles.userPostContainer}>
<AnimatedFlatList
showsVerticalScrollIndicator={false}
data={userPosts}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{
paddingTop: 250,
paddingBottom: 100,
}}
scrollEventThrottle={16}
onScroll={props.scrolling}
ListEmptyComponent={emptyPosts}
onEndReachedThreshold={0.5}
onMomentumScrollEnd={() => handleLoadingMore()}
ListFooterComponent={
loadingMore && <ActivityIndicator size="large" animating />
}
/>
</View>
);
}
FlatList
return (
<View style={styles.userPostContainer}>
<AnimatedFlatList
showsVerticalScrollIndicator={false}
data={userPosts}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{
paddingTop: 250,
paddingBottom: 100,
}}
scrollEventThrottle={16}
onScroll={props.scrolling}
ListEmptyComponent={emptyPosts}
onEndReachedThreshold={0.5}
onMomentumScrollEnd={() => handleLoadingMore()}
ListFooterComponent={
loadingMore && <ActivityIndicator size="large" animating />
}
/>
</View>
);
handleLoadingMore function
const handleLoadingMore = () => {
if (page === lastPage) {
return;
}
setPage(page + 1);
setLoadingMore(true);
};
I think the problom is when the user navigate back to the profile the state still the same and useFocusEffect fetching the same data again.
setUserPosts((prevPosts) => [...prevPosts, ...posts]);
Thanks for you help.
I have never used this hook from "react-navigation" but in the doc it says:
Sometimes we want to run side-effects when a screen is focused. A side
effect may involve things like adding an event listener, fetching
data, updating document title, etc. While this can be achieved using
focus and blur events, it's not very ergonomic.
So, every time you focus an specific route the code will run.
I am trying to fetch data when user navigate to the user profile this
is working but when user navigate back to the profile i have a
duplicate posts!
What you are doing is wrong because you are not paginating your fetches, I mean, you will need a "pointer" to the last post you fetched... with this you will avoid to fetch the data you already have. Also, the user experience will be pretty faster, as you will have lighter responses from your DB.
Anyways, I suggest you to run this code in the "useEffect" hook from React Native and try to implement a db listener or an inifinite scroll with pagination. This will not work when you focus the screen, but you will be able to fetch data every time the user refresh the FlatList, just like Instagram, Twitter, Netflix...
Take a look on this: https://aboutreact.com/react-native-flatlist-pagination-to-load-more-data-dynamically-infinite-list/
If you really need to fetch data when you focus the specific route, just implement a pagination (save in your component state an index to the last post you fetched).
UPDATE
Sorry, I didn't see that you was doing the pagination in your code. It might be a problem when you update the state of your component...
Try something like that:
const [posts, setPosts] = useState([]); // Array with the posts objects
let postsArray = useRef(posts).current; // Auxiliar array for storing posts before updating the state. Pd: this will not re-render the component as it is a reference
const loadMorePosts = () => {
// ... stuff
// const newPosts = Fetch posts
newPosts.forEach((newPost) => {
// Add the new fetched post to the head of the posts list
postsArray.unshift(newPost);
})
// The last post will be at the top of the list
setPosts([...postsArray]); // Shallow copy of the posts array to force a FlatList re-render
}
// setUserPosts((prevPosts) => [...prevPosts, ...posts]);

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