Use AsyncStorage values kept in state and display it - javascript

I'm making my first app, "mark what album you've listened" kind of app. I used iTunes search API and for every album listened, I create a key with AsyncStorage using the ID and for the value, the url to the artwork.
So here is the question: I'm stuck at the last step of the app. I want to display all the artwork of all the albums I've listened. For that, I would like to make a foreach loop that for every element in listened, it would take its URL (now that it only contains URLs), put it in an Image tag, return it and display it... But, can I do that?
For that, I created a state called listened. It takes all the AsyncStorage thanks to this function:
importData = async () => {
try {
const keys = await AsyncStorage.getAllKeys();
const result = await AsyncStorage.multiGet(keys);
console.log(result)
//listened takes all asyncstorage data
this.setState({listened: result.map(req => JSON.stringify(req[1]))});
} catch (error) {
console.error(error)
}
}
Then I made a renderArtwork() function that returns the state when I arrive to the Navigation. For now, it just displays all the URLs:
renderArtwork(){
this.importData();
return(
<Text>{this.state.listened}</Text>
)
}
And the "main":
render() {
return(
<View style={styles.main_container}>
{this.renderArtwork()}
</View>
)
}
Thank you for your help

It better to move the importData() to your componentDidMount which will call and get the data from asyncstorage when the screen is mounted.
As for displaying the images, Lets say that your current array 'listened' has the below format
listened = ['url1','url2'];
renderArtwork() {
this.importData();
return this.state.listened.map((url) => (
<Image
style={{
width: 50,
height: 50,
}}
source={{
uri: url,
}}
/>
));
}
You can simply map and show all the images in your array, Also the JSON.stringify part wont be necessary as its already a string.

Related

Why is my useEffect function called only once?

I am rendering a list of Trips objects inside a FlatList. So I have a screen named Network where I have FlatList which represents each of the trips. My render method:
return (
<View style={styles.viewStyle}>
<FlatList
numColumns={1}
horizontal={false}
data={trips}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<View key={index}>
<Trip trip={item} = />
</View>
)}
/>
</View>
);
Inside my Trip component is the trip information. Trip's name AND trip's geo locations. From those geolocations I want to get the trip's city and country. To do so I call expo's Location API inside my useEffect function, for each trip:
let response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
However, it seems that this function id being called only once for the very last trip, from all trips inside my FlatList. This is how my Trip.js component looks like:
import React, { useState, useEffect } from "react";
import { Text, TouchableOpacity } from "react-native";
import * as Location from "expo-location";
const Trip = ({ trip }) => {
const [city, setCity] = useState(null);
const [country, setCountry] = useState(null);
const { latitude, longitude } = trip;
console.log("trip name: ", trip.placeName);
console.log("latitude: ", latitude);
console.log("longitude: ", longitude);
if (!trip) {
return null;
}
useEffect(() => {
console.log("calling use effect from trip summary: ", trip.placeName);
async function fetchLocationName() {
console.log("calling async function");
let response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
console.log("response: ", response);
setCity(response[0].city);
setCountry(response[0].country);
}
fetchLocationName();
}, [trip.id]);
return (
<TouchableOpacity style={{ flexDirection: "row", flexWrap: "wrap" }}>
<Text>
<Text style={styles.textStyle}>{trip.placeName} </Text>
<Text style={styles.textStyle}>near </Text>
<Text style={styles.textStyleHighlithed}>{city}, </Text>
<Text style={styles.textStyleHighlithed}>{country} </Text>
</Text>
</TouchableOpacity>
);
};
export default Trip;
I put so many console.logs because I wanted to be sure that I have trip.longitude and trip.latitude which, indeed, I have. What I see printed on the console:
latitude: 126.3936269
longitude: 59.3397108
latitude: 71.34165024
longitude: 129.7406225
calling use effect from trip summary: trip one
calling async function
calling use effect from trip summary: second trip
calling async function
response: Array [
Object {
"city": "some city",
"country": "some country",
...
},
]
And indeed on my screen I see only the very last trip's city and country being shown.
How to make sure that my useEffect function is being called for every single trip, not just the last one?
Your logs show that useEffect is being called twice:
calling use effect from trip summary: trip one
calling async function
calling use effect from trip summary: second trip
calling async function
So it's not the useEffect that's the problem. The issue is that you're never getting a return value from Location.reverseGeocodeAsync for one of your calls.
Looking in the Expo docs for Location, you can see the following warning:
Note: Geocoding is resource consuming and has to be used reasonably. Creating too many requests at a time can result in an error, so they have to be managed properly. It's also discouraged to use geocoding while the app is in the background and its results won't be shown to the user immediately.
In the iOS code for expo-location, the following line gets printed if there are too many calls: Rate limit exceeded - too many requests. If you're seeing that line, you need to make a way to space out these requests.
reverseGeocodeAsync also takes an options argument that allows you to use Google's location service instead ({ useGoogleMaps: true }).
So in summary, here are two things to try. You can rewrite your useEffect to explicitly catch errors in case they're not showing up (removed logs for brevity):
useEffect(() => {
async function fetchLocationName() {
try {
const response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
setCity(response[0].city);
setCountry(response[0].country);
} catch (error) {
console.error(error);
};
}
fetchLocationName();
}, [trip.id]);
And if you are seeing the rate limit error, you would need to build a request queue that spaces out the calls enough to avoid that.
Or you can try using Google's service, which would be the same except for the line that calls reverseGeocodeAsync:
useEffect(() => {
async function fetchLocationName() {
try {
const response = await Location.reverseGeocodeAsync({
latitude,
longitude,
}, { useGoogleMaps: true });
...
You can write your async function like this: try this
useEffect(async()=>{
console.log("calling use effect from trip summary: ", trip.placeName);
let response = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
console.log("response: ", response);
setCity(response[0].city);
setCountry(response[0].country);
},[trip.id])
useEffect runs after component gets rendered on the screen as an effect for the dependency you put in the array, DO ADD ALL THE CONSOLE LOGS OF THE TRIP COMPONENT
in the question.
console.log("trip name: ", trip.placeName);
-------------------------
console.log("calling use effect from trip summary: ", trip.placeName);
Even if you acheive the functionality you intend to, Calling an api from every item of flatlist is not a good approach as it will reduce performance. A better way would be to call the api for particular set of latitude and longitude on some user interaction event. that would be singular, synchronous and performant.

Displaying image from firebase in react native (hitting error)

On one screen I'm taking an image and it's uploading to firebase but on another page when I'm trying to donwload the image to display it i'm hitting an error but the error it goes to something random. If I copy and paste the url into the image src it works.
I can also console log details from the image but for some reason hitting an error displaying it.
export default function DisplayPage() {
const imageRef = firebase.storage().ref().child('images');
const sampleImage = imageRef.getDownloadURL();
return (
<View>
<Image source={{ uri: sampleImage }} style={{ width: 350, height: 350 }} />
</View>
);
}
I'm uploding the file in another page
const ref = firebase
.storage()
.ref()
.child('images')
}
I want to just have a page that displays all of the images in the file that I'm in but I can't even get one image to show up.
What am I doing wrong here?
Edit:
The code error that I get it, it just refers to some random file within expo and prevents the app from booting (hence the reason why I didn't post it) but the response below actually answered my question. The only thing I'm wondering is how would you display an entire folder instead of just one image. Would that go inside of the child()? I have tried it both in child() and ref() and failed at both.
Thank you for all the help!
I have wrapped getDownloadURL method in async because it would take time to get an image from it and the view would be render initially on page load so it wouldn't render the image until it gets from getDownloadURL. So, I have used state for it which renders view again when state reset the image, and this time it loads the image successfully.
export default function DisplayPage() {
const [sampleImage, setSampleImage] = useState(); // set state here
const getSampleImage = async () => {
const imageRef = firebase.storage().ref().child('images');
const url = await imageRef.getDownloadURL().catch((error) => { throw error });
setSampleImage(url);
}
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
getSampleImage();
});
return (
<View>
{sampleImage ? <Image source={{ uri: sampleImage }} style={{ width: 350, height: 350 }} /> : null }
</View>
);
}

React Native load data from API using hooks

Im new in ReactNative and I'm trying to take some data from here https://www.dystans.org/route.json?stops=Hamburg|Berlin
When I try console.log results it return full API response. I dont know why in first results.distance works and return distance, but when I'm trying to do it inside FlatList nothing is returned. Sometimes it works when i want to return only item.distance but can't somethnig like <Text>{item.stops[0].nearByCities[0].city}</Text> nowhere in my code also in console. Im getting error:
undefined is not an object (evaluating 'results.stops[0]')
imports...
const NewOrContinueScreen = ({ navigation }) => {
const [searchApi, results, errorMessage] = useDystans();
console.log(results.distance);
return (
<SafeAreaView forceInset={{ top: "always" }}>
<Text h3 style={styles.text}>
Distance: {results.distance}
</Text>
<Spacer />
<FlatList
extraData={true}
data={results}
renderItem={({ item }) => (
<Text>{item.distance}</Text>
// <Text>{item.stops[0].nearByCities[0].city}</Text>
)}
keyExtractor={item => item.distance}
/>
<Spacer />
</SafeAreaView>
);
};
const styles = StyleSheet.create({});
export default NewOrContinueScreen;
And here is my hook code:
import { useEffect, useState } from "react";
import dystans from "../api/dystans";
export default () => {
const [results, setResults] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const searchApi = async () => {
try {
const response = await dystans.get("route.json?stops=Hamburg|Berlin", {});
setResults(response.data);
} catch (err) {
setErrorMessage("Something went wrong with useDystans");
}
};
useEffect(() => {
searchApi();
}, []);
return [searchApi, results, errorMessage];
};
As the name implies, FlatList is designed to render a list. Your API endpoint returns a JSON Object, not an Array, so there's nothing for the FlatList to iterate. If you want to show all the stops in the list, try passing in the stops list directly.
<FlatList
data={results.stops}
renderItem={({ item }) => (<Text>{item.nearByCities[0].city}</Text>)}
/>
Some side notes: (1) The extraData parameter is used to indicate if the list should re-render when a variable other than data changes. I don't think you need it here at all, but even if you did, passing in true wouldn't have any effect, you need to pass it the name(s) of the variable(s). (2) The keyExtractor parameter is used to key the rendered items from a field inside of them. The stop objects from the API don't have a member called distance so what you had there won't work. From my quick look at the API response, I didn't see any unique IDs for the stops, so you're probably better off letting React key them from the index automatically.

react-native flatlist images flicker when list state updating

I have populated a FlatList with data fetched from Google's firebase backend. The implementation is rather standard, here's a stripped down version:
export default class Day extends Component {
state = { data : [], today: false }
componentWillMount = async () => {
const { today } = this.state;
const { calendarDb } = this.props
await calendarDb.onNewAgenda({
day : today
, then: this.parseOnListed
})
}
parseOnListed = blob => {
const { data } = this.state;
data.push(blob)
this.setState({ data: data })
}
renderItem = ({ item }) =>
<Hour data = {item}/>
render = () =>
<FlatList
data = {this.state.data}
renderItem = {this.renderItem}
keyExtractor = {item => item.ID}
/>
}
The issue is that every time a new blob is pushed into data, the <Image/> component in <Hour data={item}/> flickers. This makes the list a no-go in terms of user experience. What gives? <Hour/> is standard as well, and more or less look like this:
const Hour = ({ data }) =>
<View>
<Image source={{uri:data.uri}}/>
<Text> {data.name} </Text>
</View>
The content of <Text> does not flicker, only the image from <Image .../>
Check whether keyExtractor is getting unique ID or not.
The flat list is re-rendering on state update and images are downloaded again. Because, each row is not uniquely identified as said in comments by #Guruparan Giritharan.
I found another reason that triggers this issue, of the FlatList flikering on React native. In my case, it happened every time I updated/changed the state of any function component. So, for instance, I was keeping the fetch results (data) and the next-page-id (for the next paginated fetch) in two separate function components:
const [data, setData] = useState([]);
const [pageId, setPageId] = useState(null);
Hence, every time would capture the results of my fetch, I would first set the data update and then the page id. It was the page id update what was causing the flicker.
const onEndReachedFetch = async () ={
fetch(pageId).then(result => {
setData(result.Data);
setPageId(result.pageId);
});
}
The fix was just to put the state data together so there is a single update instead. Then react is happy and doesn't flicker when adding new items to the list.
const onEndReachedFetch = async () ={
fetch(pageId).then(result => {
setResult(result);
});
}
Beware of any side states that you may be updating in the background, as they may also cause the flickering if they are triggered by anything on the FlatList.

React-Native: AsyncStorage.getItem not working. I have installed AsyncStorage from react-native-community

I have a screen with buttons to select the number of players for a game. Clicking any of these buttons calls a function, passes the appropriate number into the function, stores that number in async storage, and then navigates to the next screen. See code below:
//Example of the buttons:
<Text style={styles.text}>
How Many Players?
</Text>
<PrimaryButton
onPress={() => this.startRound(3)}
label="3"
>
</PrimaryButton>
<PrimaryButton
onPress={() => this.startRound(4)}
label="4"
>
</PrimaryButton>
//The function:
startRound = (num_players) => {
AsyncStorage.setItem('Num_Players', num_players);
this.props.navigation.navigate('player_names_screen');
}
On the next screen, if I try to use AsyncStorage.getItem, to get this number, so I can do things with it, I just get null. See code below:
constructor (props) {
super(props);
this.state = {
get_players: null
}
}
componentWillMount = () => {
this.getNumPlayers();
var players = this.state.get_players;
alert(players);
}
getNumPlayers = async () => {
let players = await AsyncStorage.getItem('Num_Players');
this.setState({get_players: players});
}
I'm using the alert function to see what I get from using AsyncStorage.getItem, and as I said before, it is showing "null". As it says in the title, I have installed async-storage from the react-native-community and am using "import AsyncStorage from '#react-native-community/async-storage';".
AsyncStorage can not store interger value so convert interger value into string
AsyncStorage.setItem('Num_Players', JSON.stringify(num_players));
Or
AsyncStorage.setItem('Num_Players', ''+num_players);
Please check this
https://snack.expo.io/#vishal7008/convert-variable-stored-in-asyncstorage-then-state-to-number

Categories