Display an array of image Reactjs Firebase SDK 9 - javascript

I am quite new to Reactjs and Firebase. Now im trying to display an array of image that fetching from a selected document ID in Firestore Web SDK9.
For example, I have an array of image like the following figure below:
I want to display all the image with url link in "imgUrls" field. At the first time, I let user choose their options with an unique ID and link it to the new page that contains all the information with that ID. Here is my code:
<Link to={`/view/${currentItem.id}`}>
{currentItem.data.dateCreate}
</Link>
Then, I fetched all the data related to this ID:
useEffect(() => {
const fetchDocById = async () => {
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
setUser({
...docSnap.data()
})
;
} else {
setUser({});
}
}
fetchDocById()}, [id]);
Then, I display all the information related to this ID, for example:
<p>Creator: {user.creator}</p>
<p>Creation Date: {user.dateCreate}</p>
These line of codes are working very well. However , I have an issue with displaying the image's dataset. I have try to use: <img src={user.imgUrls}></img>. This code worked with the array of only one item on this. If the array is more than one, it displayed nothing. I had took the picture of this error:
Besides this, I have tried to mapping the array by using:
<div>
{ user.imgUrls.map((url) => (<img src={url}> </img>)) }
</div>
But it said that Cannot read properties of undefined (reading 'map').
Could you please try to help me in this issues. I really do appreciate that.
Thank you so much!

useEffect is executed after the initial render of the component. You may have initialized user state with {}. So, when the component is rendered for the first time, user.imgUrls is undefined, cause there is no property named imgUrls in user object. And undefined doesn't have any function called map. That's why the error was shown.
You can solve it in many ways. But what's common in each approach is that don't call map function until user has a property called imgUrls.
Solution 1:using logical operator
<div>
{Array.isArray(user.imgUrls) && user.imgUrls.map((url) => (<img src={url}> </img>)) }
</div>
Solution 2:using ternary operator
<div>
{Array.isArray(user.imgUrls) ? user.imgUrls.map((url) => (<img src={url}> </img>)):null }
</div>
Solution 3:using optional chaining
<div>
{user.imgUrls?.map((url) => (<img src={url}> </img>)) }
</div>

Related

Having trouble displaying object property

I'm building a small pokedex using PokeAPI. I'm retrieving data from an endpoint and building my own object. The function that I've built for the latter runs on page load as follows:
$: pokemon = []
//fetch pokemon from PokeAPI
function getPokemon(){
let index = 0
fetch("https://pokeapi.co/api/v2/pokemon?limit=151") //
.then(response => response.json()) //
.then(allpokemon => {
allpokemon.results.map((poke) =>
pokemon = [...pokemon, {
index: index++,
name: poke.name
}]
)})
}
//populate pokemon array on page load
getPokemon()
I'm able to console log the object and traverse it in devtools, and also able to JSON stringify it and display within html. But whenever I try to retrieve a specific attribute, I receive a 500 error. For instance, I'm trying to display the current pokemons name in an h1 tag :
<h1>{pokemon[currentPokemon].name</h1> // this doesn't work
whereas
<h1>{pokemon[currentPokemon]</h1> //this does
This can't be too complicated of a fix, I just don't know what I'm missing, because I've been traversing objects for what I thought was the correct way for years now. Thank you.
This line is missing a curly brace at the end
<h1>{pokemon[currentPokemon].name</h1>
Besides that pokemon will initially be an empty array, so independent of currentPokemon the value will be undefined and accessing the key name will give the error Cannot read properties of undefined (reading 'name') which could be solved by adding a questionmark (Optional chaining)
<h1>{pokemon[currentPokemon]?.name}</h1>
There's no need for the $:, just let pokemon = []
.map() returns an array, so instead of setting pokemon inside the function better assign it the result. The index would be available as second parameter inside .map()
.then(allpokemon => {
pokemon = allpokemon.results.map((poke, index) => {
return {
name: poke.name, // or ...poke, to keep all fields
index
}
})
Writing the index in the data might not be neccessary if an #each loop is used to display the data
{#each pokemon as p, index}
<p>
{index} - {p.name}
</p>
{/each}
Try this:-
<h1>{pokemon[currentPokemon]['name']}</h1>

Endless promise loop when trying to fetch CSV data with JavaScript

I know that there are many posts out there about how to use fetch and returning promises from async functions. I've read that you should move the bit of the code that relies on the fetched data/content into the function, but I can't see how that's possible with my setup. So before you close my question as a duplicate, I ask that you read it entirely and try to remember what it was like when you were new to programming and needed some help.
I'm building a React app in which I want to graph a bunch of CSV data depending on user input. I'm trying to fetch the CSV data, pull the two variables out that I want, and then pass it into a VictoryChart. This bit of code successfully fetches the data, converts it to an array separated by the record, and then creates a new array of objects (the data input VictoryChart requires) with only the two fields I want to be my variables:
async function getData(filePath) {
let dataArray = []
await fetch(filePath)
.then(response => {
return response.text()
})
.then(data => CSVToArray(data))
.then(data => {
for (let i in data) {
dataArray.push(
{ DateTime: data[i][0], OATemp: data[i][1]}
)
};
return dataArray
})
.catch(error => alert(error))
}
As suggested by several sources, I was going to try to put the VictoryChart into the function after the return dataArray to keep everything that relied on the function in the function, but I don't see how it's possible to control the layout of my page by trying to call a function that then generates a graph. I don't see how you build the DOM with that (am I even using that right?). I'm new to React (and generally an inexperienced programmer) so maybe that's part of the problem.
The only thing that makes sense to me is for the async function to return the data so I can pass it into the VictoryChart. But it seems impossible to extract anything returned from the function out of the promise that it gets wrapped into. I've been told to use .then but that just ends up returning a promise. It seems so incestual... To be specific my most recent attempt looks something like this (just using alert to "see" what's happening):
alert(getData('./2021WeatherData.csv').then(data => {return data}))
Hopefully, I've just not understood how to properly use .then as suggested by others. I have sunk many, many hours into trying dozens of different versions of this and googling every synonym of the problem I can think of. I know I'm missing something so I appreciate any help.
***Providing additional code
Below is what I understand to be the DOM for my React app (please correct me where I'm wrong in my wording etc.). This renders my app to the browser.
return (
<div className="App">
<header className="App-header">
<div className='Header-button-container'>
<button onClick={() => alert('Weather')}>
Weather
</button>
<button onClick={() => alert('1st Floor')}>
1st Floor
</button>
<button onClick={() => alert('2nd Floor')}>
2nd Floor
</button>
</div>
</header>
<VictoryChart>
<VictoryLine data={getData('./2021WeatherData.csv').then(data => { return data })} x='DateTime' y='OATemp' />
</VictoryChart>
</div>
);
To me, this dictates how things show up on the page. I notice that everything here is a tag so I don't understand how I would insert a function where the VictoryLine tag exists. The "data" attribute of the VictoryLine graph requires data as an array of objects and that's what I'm trying to output from my getData function (but it keeps getting wrapped in a darn promise!!). I am able to make it work just fine with a dummy data set I declare within the app, but I'm stuck when trying to fetch data. And since I can't get the data "out" of the function (it seems all that's possible is doing things inside of the function), I'm just stuck and perplexed. The intent here is to be able to click a button and fetch the relevant data to graph.
It sounds like you're just having trouble getting the CSV result passed along to your graphing function. First, you need to return your value from getData; currently it isn't returning anything:
async function getData(filePath) {
let dataArray = []
return fetch(filePath)
...
}
Then you can pass your result in one of two ways:
function theGraphFunction(data) {
// this graphs your data
}
getData("./path/to/data.csv").then(data => theGraphFunction(data));
or
(async () => {
const data = await getData("./path/to/data.csv");
theGraphFunction(data);
})()
Now that you've updated your question to include information about what you're actually trying to do, you could do something like this:
import {useEffect, useState} from "react";
function App() {
const [graphData, setGraphData] = useState(null);
useEffect(() => {
getData("./path/to/data.csv").then(data => setGraphData(data));
}, [])
// only include this if you don't want to render the component
// until data is loaded
if(!graphData) {
return null;
}
return (
<div className="App">
<header className="App-header">
<div className='Header-button-container'>
<button onClick={() => alert('Weather')}>
Weather
</button>
<button onClick={() => alert('1st Floor')}>
1st Floor
</button>
<button onClick={() => alert('2nd Floor')}>
2nd Floor
</button>
</div>
</header>
<VictoryChart>
<VictoryLine data={graphData} x='DateTime' y='OATemp' />
</VictoryChart>
</div>
);
}

Cannot read property '0' of undefined when I change back tabs in React

I am working on a personal project and I am trying to fetch data from an API that I have running on my server. Before the first implementation of the code, everything works fine, even after I write my code and save it the first time it works alright. But after I do this and switch my navigation tabs back and forth once, everything breaks and I get "TypeError: Cannot read property '0' of undefined"
I would like to fetch all my data in one place and then just get what I need from it willingly.
This is the fetching function:
//i have my state here
const [rezultateEz, setRezultateEz] = useState()
//code for fetching the data
const ezFetcher = async () => {
const intrebare = await fetch('http://localhost:5000/ezquestions')
const res = await intrebare.json();
setRezultateEz(res)
}
//i then call my function inside a useEffect
useEffect(() => {
ezFetcher();
}, [])
//and so i can just test it i try to run it inside this JSX here
<div className={style.questionHolder}>
{rezultateEz[0].question}
</div>
The res property has a question, all_answers array and correct_answer inside it, which I want to access more easily with this function but I can't really do it because of the error presented earlier. How can I do that?
I tried using try catch or saving different pieces of state individually but nothing worked.
I will kindly accept any piece of advice or information given, and thank you for the time you spent reading my question
All I had to do was to set
const [rezultateEz, setRezultateEz] = useState([])

OpenWeatherMap API: cannot read property of undefined

I am trying to access the weather information from an API call to OpenWeatherMap, but I always get a "cannot read property of undefined" error.
In my App.js file I have a CallAPI function that gets passed the users coordinates and fetches the weather data. It's then passed down to my Header component with a prop of "curr".
const [currWeatherData, setCurrWeatherData] = useState({})
useEffect(() => {
navigator.geolocation.getCurrentPosition(CallAPI, showError);
}, [])
//... showError function
function CallAPI(position){
const lat = position.coords.latitude
const long = position.coords.longitude
fetch(/*api call*/)
.then(response => response.json())
.then(data => {
setCurrWeatherData(data.current)
})
}
return (
<div>
<Header curr = {currWeatherData}/>
<div>
)
In my Header.js file I am currently just trying to display the weather status.
import React from "react"
function Header(props){
return(
<div>
{/*<h1>{props.curr.weather[0].main}</h1>*/}
{console.log(props.curr.weather)}
</div>
)
}
The beginning of the json file from the API looks like this.
json file
in "current", there is a weather property that is an array with one element which is an object, thus I would assume the correct way to access the "main" property would be "current.weather[0].main". However, I get the error "cannot read property '0' of undefined" when I try to console.log or return that. The strange part is that when I console.log "current.weather" it prints an array with an object to the console.
console
I've tried storing "current.weather" in a variable before accessing its 0th index and I've tried passing "currWeatherData.weather" as the prop in my App.js file, both of which I don't think change anything. I'm not really sure where to go from here, can anyone help?
EDIT: after an hour or so of console.log debugging i figured out my problem. I learned that when using hooks, useState triggers rerenders the same way this.setState did, meaning each time I set the state, it rendered my Header component. I'm guessing the API call didn't finish before rendering it, so the prop was passed as undefined. I solved this by adding an isLoading state and setting it to false after the API call,
//... code above
.then(data => {
setCurrWeatherData(data.current)
setIsLoading(false)
})
and in my return, I added a conditional statement
<div>
{!isLoading && <Header curr = {currWeatherData}/>}
</div>
I skimmed through https://medium.com/swlh/how-does-react-hooks-re-renders-a-function-component-cc9b531ae7f0 to help
In your App.js you have the following code:
return (
<div>
<Header curr = {currWeatherData}/>
<div>
However, this does not take the async API call into account. Therefore currWeatherData is null because the API call has not yet completed.
You need to consider the lifecycle and only try to render the data after the API call completes. There are various ways in React to do this, depending on your overall app/component design.
There's an example here based on componentDidMount.
since you are using a functional components. I suggest you add useEffact(()=> { your api call}, [] ) and this should work. because currently your code is not getting the data you want! hopefully this helps.

Weird behavior on array map after state update

Hope you're all keeping well in the current climate!
I'm working with Firebase and ReactJS at the moment and have encountered an odd behavior with the state update in react and the array map functionality in JavaScript. The code below shows my logic for listening to updates from the firestore database:
listenForContentChanges = async () => {
let content = [];
await db.collection('clients').doc(this.props.client)
.collection('projects').doc(this.props.id)
.collection('content').orderBy('created').onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(async change => {
if (change.type === 'added') {
content.push({
id: change.doc.id,
uploaded: change.doc.data().created.seconds
});
}
if (change.type === 'removed') {
content = content.filter(arrayItem => arrayItem.id !== change.doc.id);
}
});
this.setState({
content: content,
addingContent: false
});
});
}
As you can see, an array is populated with 'content' information just the ID of the document and a field containing the time in seconds of when that document was created. This gives me an array back:
0: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}
I need this array sorted so the most recent document comes first and because Firebase doesn't offer this as a query parameter (you can only sortBy and not change the direction) I copy the array to a new array and then loop over that instead to get everything in the correct order:
const sortedArr = [...this.state.content];
sortedArr.reverse();
/// IN THE RETURN: ///
{sortedArr.map((content, index) => (
<Content key={index} />
))}
This works okay with no issues. My problem is that now when a new element is added/one is taken from the state array this.state.content and the component is re-rendered, I am seeing a weird behavior where the last elements, instead of showing the new data, seem to duplicate themselves. Please see an example timeline below:
As you can see, when there is a new document added in firestore, the code shown above fires correctly, pushing a new array element onto the state and re-rendering the component. For those who are interested, yes the state is being correctly updated, this is what is being logged inside the render function after the state update:
0: {id: "x07yULTiB8MhR6egT7BW6ghmZ59AZY", uploaded: 1586323587}
1: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}
You can see there index 1 is new which is the document that has just been added. This is then, reversed and mapped in the return() function of the render() but causes the weird behavior shown in the image above. Any help on what may be causing this would be a great help, I've been banging my head against the wall for a while now!
Using array index as react key fails when the rendered array is mutated (i.e. unstable). React sees the new length, but the key for the first element is still the same so it bails on rerendering (even though it's the new element).
Try instead to always use unique keys within your dataset, like that id property.
{sortedArr.map((content, index) => (
<Content key={content.id} />
))}

Categories

Resources