Show image from Firebase with React Native - javascript

I want to see the link to my image in the imageUrl variable.
When I log sampleImage, I see JSON-like data and I can see a link in this data's i section.
But when I log imageUrl, it returns undefined.
render() {
const uid = '12345';
const imageRef = firebase.storage().ref(uid).child(uid);
const sampleImage = imageRef.getDownloadURL().then();
const imageUrl = sampleImage.i;
console.log(sampleImage);
console.log(imageUrl);
return (
<View>
<Image style={{ width: 55, height: 55 }} source={{ uri: null }} />
</View>
);
}
}

Getting an image is a network request so it takes a little while. If you want to log the image (and you don't use async/await) then you have to do it inside the .then()
const sampleImage = imageRef.getDownloadURL().then(result => console.log(result));
Rather than doing all of this in the render method, do it when the component mounts and store the URL in state. That way you can just reference the state object in your render method.

Related

How do I display the data properly from the snapshot to expo

I finally managed to read the children of the node I wanted but I got another question, how do I display them on my screen properly. I tried to save the snapshot under setToDoData but I cannot bring it out
My code:
const SubScreen3 = ({route}) => {
const paramKey = route.params.paramKey1
const [todoData, setToDoData] = useState([])
useEffect (() => {
const starCountRef = ref(db, "food/" + paramKey);
onValue(starCountRef, (snapshot) =>{
console.log(snapshot.val().Description);
console.log(snapshot.val().Halal);
console.log(snapshot.val().OH);
console.log(snapshot.val().Location);
console.log(snapshot.key);
setToDoData(snapshot)
})
}, [])
return (
<View style = {styles.container}>
<Text style = {styles.header}>{snapshot.key}</Text>
</View>
)
}
export default SubScreen3
Here is my log from the console:
But when I run it, i get the output of Snapshot cannot be found
How do I solve this?
Update: I changed according to the solution provided but it gives me Error: Objects are not valid as a React child (found: object with keys {Description, Halal, Location, OH}). If you meant to render a collection of children, use an array instead. so i used the same method I did for another page to display multiple stuff but I have another issue which is TypeError: undefined is not a function (near '...todoData.map...')
const SubScreen3 = ({route}) => {
const paramKey = route.params.paramKey1
const [todoData, setToDoData] = useState([])
useEffect (() => {
const starCountRef = ref(db, "food/" + paramKey);
onValue(starCountRef, (snapshot) =>{
console.log(snapshot.val().Description);
console.log(snapshot.val().Halal);
console.log(snapshot.val().OH);
console.log(snapshot.val().Location);
console.log(snapshot.key);
setToDoData(snapshot.val());
})
},)
return (
<View style = {styles.container}>
{
todoData.map((item,index) => {
return(
<View key ={index}>
<Text style = {styles.header}>{item.key}</Text>
<Text style = {styles.header}>{item.val().Description}</Text>
<Text style = {styles.header}>{item.val().Location}</Text>
<Text style = {styles.header}>{item.val().OH}</Text>
<Text style = {styles.header}>{item.val().Halal}</Text>
<TouchableOpacity
//onPress={() => navigation.navigate("SubScreen1", {paramKey:value})}
style = {styles.button}
>
<Text style = {styles.buttonText}>How to go?</Text>
</TouchableOpacity>
<TouchableOpacity
//onPress={}
style = {styles.button}
>
<Text style = {styles.buttonText}>Reviews</Text>
</TouchableOpacity>
</View>
)
})
}
</View>
)
}
export default SubScreen3
Expected screen output: Something like this
What actually happen:
My reading of the DataSnapshot API seems to indicate that you want to call snapshot.val() to extract the data from the snapshot. Personally, I would be storing that data in the state variable rather than the snapshot itself:
setToDoData(snapshot.val());
Then in your JSX, you should be referring to the todoData variable, not the snapshot variable (which does not exist outside of the onValue() callback function.
Also, I would recommend NOT initializing your state variable to the value []. Simply leave the initialization blank, and check for a "falsey value" in your JSX. Otherwise your code will not be able to distinguish between "has not received a value yet" (keep it as undefined) and "received a value but it has no data" (i.e. your query returns []).
ANSWER TO THE EDITED QUESTION:
Your JSX has been updated to now use todoData.map(), meaning that todoData is expected to have an array. But the code we gave in the earlier answer simply puts an object into todoData.
To support your newer JSX, you would modify the code where you put the data into the state variable such that you are appending to an array:
setToDoData(curVal => {
return [...curVal, snapshot.val()]
});
This new code now uses the second form of the "set state function" where you provide a callback function. When the "set state function" runs, it will call the callback function passing the "current value" of the state variable. From that, we expand it into a new array and append the new data from the snapshot.
Please, if there is a new problem, post a new SO post. Editing posts like this is not advisable and becomes very hard to follow after just a couple of iterations.

timing issue, console.log show Array element but length is zero

In FeedScreen, I try to get "userData" from redux store and feed it into a Flatlist.
But nothing is shown. I check console.log with Chrom dev tool, I can see the element in "userData" but .length is zero and "userData[0]" is undefined.
If I refreshed the app with expo, the flatlist is shown.
I think it is related to timing as "userData" is fetched from firebase store and FeedScreen is nested in MainScreen Tab Navigator.
How to fix it?
the code shown below is extracted only from the relevant section, and I log the redux store in MainScreen.js. The redux reducer is working as expected.
FeedScreen.js
export default function FeedScreen(props,{ navigation }) {
const [useData2, setUserData2] = useState({});
const userData = useSelector(state=> state.userState.userdata)
console.log(userData)
console.log(userData.length)
}
MainSceen.js
....
let tempPostArr2=[];
let subUserPost = followingList.map(uid => {
const q = query(collection(db, "post", uid, "userPosts"), orderBy("timestamp", "asc"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
let tempPosts = querySnapshot.docs.map((doc) => {
const data = doc.data();
const postId = doc.id;
return { postId, ...data, uid }
})
tempPostArr2.push(tempPosts)
})
})
dispatch(fetchAllUsersPost(tempPostArr2))
return (
<Tab.Navigator initialRouteName='Feed' labeled={false}>
<Tab.Screen name="Feed" component={FeedScreen}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={size} />
),
}} />
</Tab.Navigator>
)
Minor point about console.log: if you feed it an object directly like you've done, it tends to be "live" if you try to open it to look at object contents. So if you want to truly know what the object was at the time of logging, console.log(JSON.stringify(x)) is necessary.
I found the answer, onSnapshot provides a callbackfunction. The dispatcher runs before the callback function runs. I make a state change in the callback function and use useEffect to relaunch the dispatcher.
reference

Unable to Display Image from S3 Using Signed URL : React/Amplify

I am attempting to retrieve and display images from S3 using a signed URL. I can log the Signed URL to the console, click it, and it will download the image locally. The downloaded image is not corrupted in any way.
: Component Hierarchy
App
- Maps
-- Map
The App component manages filtering of maps, the filtered maps state is then passed to the Maps component which dynamically renders Map components.
export default function Maps({ maps }) {
return (
<Carousel
autoPlay={false}
PrevIcon={<ArrowCircleLeftIcon fontSize='large' />}
NextIcon={<ArrowCircleRightIcon fontSize='large' />}
>
{maps.map((map, i) => (
<Map key={i} map={map} />
))}
</Carousel>
)
}
My Map component holds state containing the Maps Image URL, and a useEffect hook which calls a getImg function.
export default function Map({ map }) {
const [mapImg, setMapImg] = useState(null)
useEffect(() => {
if (map.uri) {
getImg(map.uri).then((res) => {
console.log(res)
setMapImg(res)
})
}
}, [map.uri])
return (
<CardMedia
component='img'
alt='random nature'
height='550'
image={mapImg}
/>
)
Finally, the getImg function.
export const getImg = (uri) => {
const uriToS3Path = (uri) => {
let path = uri.substring(0, 6)
const file = uri.substring(6) + '.TIF'
path = path.split('').join('/') + '/'
return path + file
}
const filePath = uriToS3Path(uri)
return Storage.get(filePath, { contentType: 'image/tiff' })
}
Don't be confused by map.uri, basically this property determines the key for the S3 object, hence the uriToS3Path function as above. I don't receive any debug errors, I can retrieve the link but the image does not display. Is it due to some asynchronous activity or should the entire approach to fetching be altered?
Many Thanks.
Update:
Inspecting the Application tab within the developer console. You can see that the S3 Image is listed (92.TIF), but the preview is broken.

useEffect not being called and not updating state when api is fetched

I'm fetching data from a weather api using useEffect hook and declaring the dependency correctly as well. My state is still not being updated and I get errors in my render function because of that. I've pretty much tried everything from getting rid of the dependency array to declaring multiples in the dependency array. I don't know what's wrong with my function. The API's JSON response is in this format:
{
location: {
name: "Paris",
region: "Ile-de-France",
},
current: {
last_updated_epoch: 1564279222,
last_updated: "2019-07-28 04:00",
temp_c: 16,
temp_f: 60.8,
is_day: 0,
condition: {
text: "Clear",
icon: "//cdn.apixu.com/weather/64x64/night/113.png",
code: 1000
},
wind_mph: 6.9,
wind_kph: 11.2
}
}
and this is what my code looks like:
const Weather = ({ capital }) => {
const [weather, setWeather] = useState(null);
useEffect(() => {
console.log("useEffect called");
const getWeather = async () => {
try {
const res = await axios.get(
`http://api.apixu.com/v1/current.json?key=53d601eb03d1412c9c004840192807&q=${capital}`
);
setWeather(res.data);
} catch (e) {
console.log(e);
}
};
getWeather();
}, [capital]);
console.log(weather);
return (
<Card style={{ width: "18rem", marginTop: "25px" }}>
<Card.Img variant="top" src={weather.current.condition.icon} />
<Card.Header style={{ textAlign: "center", fontSize: "25px" }}>
Weather in {capital}
</Card.Header>
</Card>
)
}
I expect to get to be shown image of the icon but I get this error message in my render function:
TypeError: Cannot read property 'current' of null
Weather
src/components/Weather.js:26
23 |
24 | return (
25 | <Card style={{ width: "18rem", marginTop: "25px" }}>
26 | <Card.Img variant="top" src={weather.current.condition.icon} />
| ^ 27 |
28 | <Card.Header style={{ textAlign: "center", fontSize: "25px" }}>
29 | Weather in {capital}
and my console.log(weather) return null, the original state even though its being called after useEffect() and console.log(useEffect called) does not log at all which mean useEffect is not being called.
The error message gives it away, Cannot read property 'current' of null, the only place where current is called is in weather.current in the src of Card.Img, so we deduce that weather was null during the render.
The reason this happens is because the api call is asynchronus, it doesn't populate the state immediately, so the render happens first and tries to read .current from the initial weather state null.
Solution: in your render method, make sure not to read weather.current while weather is null.
You can for example use {weather && <Card>...</Card} to hide the whole card until the data is loaded and show a loading indicator, or you can use src={weather && weather.current.condition.icon} as a quick workaround.
const Weather = ({capital}) => {
const [weather, setWeather] = useState(null);
useEffect(() => {
console.log("useEffect called");
const getWeather = async () => {
try {
const res = await axios.get(
`http://api.apixu.com/v1/current.json?key=53d601eb03d1412c9c004840192807&q=${capital}`,
);
setWeather(res.data);
} catch (e) {
console.log(e);
}
};
getWeather();
}, [capital]);
console.log(weather);
return (
<Card style={{width: "18rem", marginTop: "25px"}}>
<Card.Img variant="top" src={weather && weather.current.condition.icon} />
<Card.Header style={{textAlign: "center", fontSize: "25px"}}>
Weather in {capital}
</Card.Header>
</Card>
);
};
I had the same puzzling issue one time
You can try adding a key prop on the component when it is created in the parent code
<yourcomponent key="some_unique_value" />
This is because in most cases, when your component is reused, based on the way it is created, it may usually re-render it with some changes instead of creating it again when you reuse it, Hence the useEffect is not going to be called. eg in SwitchRoute, loops, conditionals...
So adding a key will prevent this from happening. If it is in a loop, you need to make sure each element is unique, maybe by including the index i in the key if you can't find any better unique key.
So, I was facing a similar issue, where it seemed like useEffect hook was not getting invoked at all. The concerned code snippet is given below:
Within my functional component, this was the sequence of the appearance of the code:
a variable const facetFields = []; declaration which was supposed to be set by a AJAX call from within useEffect hook
useEffect hook within which the above variable was getting set with AJAX.
useEffect( ()=>{
console.log('FacetTreeView: useEffect entered');
facetFields = getFacetFieldNames(props.tabIndex);
}, []);
JSX code that uses the variable.
return (<MyComponent> { facetFields.map((facetLabel, index) => {
populateFacetInstances(facetLabel) }) } </Component>)
With this code, the console statement inside the hook was not printing at all. Also, I kept getting a undefined error while map'ing the facetFields. So, it turns out that the useHook is called after the component is rendered. So, during the rendering, the variable facetFields is undefined.So, I fixed this by adding this line to my JSX rendering part of code:
facetFields && facetFields.map(.....
Once, I made the above change, the control started going to the hook.So, in summary, if there is any code within JSX that is being set from useEffect hook, then its best to defer the execution of the JSX till hook execution is completed. Although, this is not mandatory, but it will make the JSX code easier to read. This can be achieved in a simple way by using a boolean state flag.
Define the state:
const [readyForRender, setReadyForRender] = React.useState(false);
2. Set state in hook.
useEffect( ()=>{
console.log('FacetTreeView: useEffect entered');
facetFields = getFacetFieldNames(props.tabIndex);
setReadyForRender(true); }, []);
Render JSX conditionally.
if(readyForRender){
return ( { facetFields.map((facetLabel, index) => { populateFacetInstances(facetLabel) }) } );
} else{
return null;
}

Why is one array populated with data but the other isn't in the console: [ ] and [ {..} {..} ] given identical datasets? [duplicate]

This question already has answers here:
array.length is zero, but the array has elements in it [duplicate]
(2 answers)
Closed 4 years ago.
So I did an interesting test because my data is not populating in my FlatList React Native element. I made two state variables: realList and fakeList:
state = {
realList: [],
fakeList: [],
}
Then in componentWillMount, the following function is run which populates two arrays called real and fake. One with data pulled from Firebase, the other hardcoded with the SAME array information:
listenForMusic = () => {
var dataRef = database.ref("music");
let real = [];
let fake = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
var url = snap.val().youtubeURL;
var vidTitle = snap.val().title;
var thumb = snap.val().thumbnail;
real.push({
videoURL: url,
title: vidTitle,
thumbnail: thumb
});
});
fake.push({videoURL: "https://youtu.be/AHukwv_VX9A", title: "MISSIO - Everybody Gets High (Audio)", thumbnail: "https://i.ytimg.com/vi/AHukwv_VX9A/hqdefault.jpg"}, {videoURL: "https://youtu.be/G-yWpz0xkWY", title: "SMNM - Million ft. Compulsive", thumbnail: "https://i.ytimg.com/vi/G-yWpz0xkWY/hqdefault.jpg"});
this.setState({
realList: real,
fakeList: fake
});
}
Then I console.log both of the arrays after the render function:
render() {
console.log("Actual", this.state.realList);
console.log("Fake", this.state.fakeList);
return (
<View>
<FlatList
data={this.state.fakeList}
renderItem={({item}) => <Text>{item.videoURL}</Text>}
/>
</View>
);
}
and I see this:
And opening both:
So my question is, why does the "real" array look empty but still has data populated inside while the "fake" array displays that it holds two objects inside of it, even before we take a look inside??
In my FlatList, if I use my fakeList of data, I can display the data on screen, but if I use my realList, nothing shows up on my screen.
EDIT: Added full code for reference:
class VideoFeed extends React.Component {
state = {
itemList: [],
}
componentDidMount() {
this.listenForMusic();
}
listenForMusic = () => {
var dataRef = database.ref("music");
let items = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
});
this.setState({ itemList: items })
}
_renderVideoItem = ({item}) => (
<TouchableWithoutFeedback
onPress={Actions.Submit}
>
<View style={styles.mediaContainer}>
<Image
source={{uri: item.thumbnail }}
style={styles.mediaThumbnail}
/>
<View style={styles.mediaMetaContainer}>
<View style={styles.topMetaContainer}>
<Text style={styles.mediaTitle}>
{item.title}
</Text>
<Text style={styles.sharedByUser}>
UNCVRD
</Text>
</View>
<View style={styles.bottomMetaContainer}>
<Icon
name='youtube-play'
type='material-community'
color='#ff0000'
size={16}
/>
<View style={styles.bottomRightContainer}>
<Icon
name='thumb-up'
size={12}
color='#aaa'
/>
<Text style={styles.metaLikeCounter}>
16
</Text>
</View>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
render() {
console.log(this.state.itemList); // this list is populated
return (
<View>
<FlatList
data={this.state.itemList}
renderItem={({item}) => {
console.log('item in render --> ', item); return (<Text>{item.videoURL}</Text>) }}
/>
</View>
);
}
}
The console window holds object references. The real is empty when the first console.log statement is executed. By the time you expand it. The events have fired. Your array is no longer empty. You can test this in the console:
let real = [];
let fake = [1, 2];
console.log(real, fake);
setTimeout(() => real.push(9))
When you inspect an object from a console log in more detail, your console will do a new lookup for that object. So what happens in your case is that at the point of your console log the array is empty, but gets populated shortly after. When you inspect it, you will see the object as it exists in memory at the time of inspection.
It is not advisable to do async stuff in componentWillMount (you will miss out on a re-render), better move that over to componentDidMount.
Edit:
Since you want your component to automatically update when your Firebase data changes, the best way to go is to make use of a property in your React class which holds the reference to your Firebase data. More information on exactly how to do this you can find here. Check the section Realtime Database listener.
class VideoFeed extends React.Component {
constructor(props) {
super(props);
this.state = {itemList: []};
this.realList = firebaseApp.database().ref(); // create property
}
componentDidMount() {
this.listenForMusic(this.realList); // pass property to listener method
}
listenForMusic = (list) => { // listener method
let items = [];
list.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
this.setState({ itemList: items }); // update state
});
}
// rest of component code ...
}

Categories

Resources