Map rendering React components with API data; "undefined" error - javascript

I am continuously running into this error with my first non-trivial React project. The error specifically is "TypeError: Cannot read property 'map' of undefined." The reason this error is happening is because React seems to load every component twice while I'm testing it.
Here's what I mean. I am trying to create a list of song names from a JSON I am receiving from the Spotify API. Each time the app loads I need to authenticate with Spotify before I can receive the data. I believe this is causing the undefined error as React is loading the component with the map function before there is data to map over.
import React from 'react';
function List(props) {
const userData = props.userData;
const favorites = Object.values(userData)[0]; // turns JSON object into usable array
// console.log(favorites)
// try {
// favorites.map( (item) => console.log(item.name));
// } catch {
// console.log("No favorites");
// }
return (
<div>
<p>Your favorite songs are:</p>
<ul>
{favorites.map( (item) => (<li key={item.id}>{item.name}</li>))}
</ul>
</div>
);
}
export default List;
If I uncomment out the try/catch statement and scrap the return statement, I get a console output that
looks like this.
So my question is how/why is this happening? What is the best way to map over data you don't have yet?

You have a couple of options, people normally either specify a default value or do only the processing when the value is defined, i prefer option 1, for example:
const favorites = Object.values(userData)[0] || []

Related

Data from store not reactive in Vue.js 3

This is my first attempt at building a web app with Vuejs. I've been trying to get data from an external JSON API and display it on my app. The JSON fetch etc is working fine. but I can't get the data to be displayed reactively on my component.
As you can read in Appraisal.js given an API link some data is populated in Appraisal.app_data. The data always has an array called items (that's just how the API is. I'll add validation later). As a proof of concept I'm trying to display the number of elements in the items array.
Since other components in my app will also use this data, I'm using an external store as the data source everywhere. One of the components calls Appraisal.setLink() on getting some user input. That part is working as expected. However the DOM contents don't change at all.
I referred to State Management for setting up the external store. I also referred to some other answers on StackOverflow with a similar issue and got the following suggestions:
The data should be initialized to undefined or null instead of {} for reactivity to work.
Properties of objects are not reactive. But by my understanding this was changed in Vue3 where it doesn't matter because proxies are in use. Either way I tried using the Object.assign({}, ..., ...) method but it did not help.
Arrow functions cannot be used in methods for reactive objects. If I remove the arrow function and put the body inside .then(function(data) {...}) it complains that this is not defined for the second then function on fetch
// --- src/components/AppraisalView.vue
<script setup>
import ItemView from './ItemView.vue';
</script>
<template>
<div v-if="app_data">{{app_data.items.length}} items in appraisal</div>
<div v-else>Enter link to get quote</div>
</template>
<script>
import {Appraisal} from '../stores/Appraisal.js';
export default {
data() {
return {
app_data: Appraisal.app_data,
}
},
}
</script>
// ---- src/store/Appraisal.js
import {reactive} from 'vue'
import {BuybackCalculator} from './BuybackCalculator.js';
export const Appraisal = reactive({
link: '',
app_data: undefined,
methods: {
setLink(value) {
if (this.link == value) return;
this.link = value;
console.log('Updating appraisal with: '+this.link);
fetch(this.link+".json")
.then((response) => response.json())
.then((data) => {
this.app_data = data;
console.log(this.app_data);
BuybackCalculator.methods.calculate_buyback(this.app_data);
});
}
}
});

How to enable persistence on reactfire?

I'd like to implement Firestore offline persistence on my PWA React app using the reactfire library.
const firestore = useFirestore().enablePersistence();
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
but running the code i get an error:
FirebaseError: Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
This component is wrapped inside a <Suspense> as mentioned in the documentation
That database read is the only one that i make in the entire app, how can i solve?
Edit.
Using the example that #Ajordat gave, I've imported the preloadFirestore function inside the App component I do get an error:
"Cannot read property 'name' of undefined".
Whereas adapting (because I cannot use hooks inside the fetch function)
the example from #DougStevenson: I've imported useFirestore function in the App component (in order to get the Firestore object) to enable persistence, and then importing it (useFirestore) into my component in order to retrieve the data, but now, I get the same error as before,
Firestore has already been started and persistence can no longer be enabled.
Edit 2:
I've tried to enablePersistence without errors, thank guys, this is my approach, let me know if it is the best:
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
And in my custom component:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let document = useFirestoreDocDataOnce(docRef);
console.log(document)
But now I do have a problem, when I log the document, the data are not emitted instantly, yeah I know that it is an asynchronous operation, but the component is wrapped inside a <Suspense>, in this way:
<Suspense fallback={<div>Loading</div>}>
<FoodComponent foodName={"Milkshake"} />
</Suspense>
But I don't see the loading text before the component is actually rendered.
Does the suspense fragment show the fallback component only while is loading the function (useFirestore) and not the actual data?
Well, I've solved, have to destructure the data, doing like that:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document)
On other JavaScript libraries for Firestore, enablePersistence() returns a promise. That means it will complete some time in the future, with no guarantees how long it will take. If you're executing the query immediately after you call enablePersistence(), without waiting for the returned promise to become fulfilled, then you will see this error message. That's because the query "beats" the persistence layer and effectively executes first.
You will have to figure out how to use that promise to wait until it's OK to make that query with persistence enabled. For example:
seFirestore().enablePersistence()
.then(() => {
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
})
.catch(error => {
console.error("enablePersistence failed", error);
})
Notice how the query will complete only after the persistence is fully enabled.
Thanks for the suggestion guys #DougStevenson and #Ajordat
In app component:
import { useFirestore } from "reactfire"
...
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
In your custom component, where you want to use Firestore:
import { useFirestore, useFirestoreDocData /* or what you want to use */ } from "reactfire"
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document);

React setState late updation with API calls [duplicate]

This question already has answers here:
if-else statement inside jsx: ReactJS
(17 answers)
Closed 2 years ago.
I am new to React and as I was creating a project, I came across a rather peculiar event with my code.
It is with the async nature of the setState which doesn't let me render data on my page like i want it to.
I am trying to display files which i have in my database already, onto my webpage. But since the state is set after a while therefore i am not able to render my files onto the screen using the map function, since when it is called it is undefined.
I implplemented a get method in so as to get the json response of the files that i want to display. But as I mount my component and setstate of the Files, it shows that it doesn't have any value in it.I know it is async but i have no idea how to handle it so that i can use map to display onto the webpage.
My code is as follows:
import React, {Component} from "react";
// import axios from 'axios';
import {Link} from 'react-router-dom';
import {Styles} from '../Styling/Styles';
class FileView extends Component {
state = {
fileViewData : {}
}
// viewFunction(){
componentDidMount(){
fetch('http://127.0.0.1:8000/api/uploads/', {
method: 'GET'
})
.then((response) => {
let data = response.json();
return data;
})
.then((data) => {
this.setState({fileViewData: data}, ()=>{
console.log("hi");
});
}).catch(error => {console.log(error)})
// console.log(fileViewData);
}
render(){
return (
<React.Fragment>
<Styles>
<div className = "appbar">
<Link to='/dashboard'>
<button className="homeBtn" label="Back" >
Back
</button>
</Link>
</div>
{/* <button label="View" onClick={this.viewFunction} >
View
</button> */}
</Styles>
//.....section not working since map is not a function of undef......//
{
this.state.fileViewData.map(item =>{
return (
<h2>{item.fields.file}</h2>
);
})
}
//.......section not working ends.........//
{console.log(this.state.fileViewData)}
</React.Fragment>
);
}
}
export default FileView;
The console output is something like this:
The empty object is returned twice and then the data is returned twice.I am not running any kind of loop either.
How should I set the value so that i am able to display my files onto the screen? TIA!
Looks like your data is an array, so your initial state should also be an array
state = {
fileViewData: [],
}
Then your check for array length will be correct, but regular javascript doens't quite work the same in JSX. You'll need to conditionally render JSX.
Conditional Rendering
{this.state.fileViewData.length
? this.state.fileViewData.map(...)
: null
}
Since it seems you don't really render anything if there is no data, you can simply map the data as array::map correctly handles empty arrays.
{
this.state.fileViewData.map(...)
}
Set state to an empty array
Remove the if condition and anonymous function declaration from your map statement
You declare that function but never invoke it also you don't need it.
if you insist on checking the length
{
this.state.fileViewData.length &&
this.state.fileViewData.map(item =>{
return (
<h2>{item.fields.file}</h2>
);
})
}
You are getting multiple console logs because when you set state in React you cause the component to render again.
As for your implementation, you probably want the initial value of fileViewData to have the same data structure as what your back end hands back. Currently you start with a plain javascript object, and then turn it into an array of objects once the response comes from your back end.
Without any consideration for a loading period, a simple thing to do to make your application not crash until data loads in would be to make fileViewData an empty array when it is initialized, not an object with no keys. Then it would have the correct prototype to have the method map.

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.

How to check if a JSON object is present in a file in JavaScript

I am building an app in react-native and have been trouble getting some trouble doing error checking. I am using redux and thunk to store a JSON file from the API that I am searching from. Sometimes, the search will lead back with a JSON file that contains an error message(most likely due to spelling error or unsure about what I am exactly searching for). I am trying to build a function in my app that will run in an componentWillMount function that first checks if the file has an error meassage and if so will send it back to the home screen to redo the search. The problem that am encountering is that I do not know what to code inorder to see what the error is or even if there is an error to begin with.
this is what the error object will look like in the JSON file and this
"error": {
"type": "NameResolutionException",
"message": "Name resolution error: Taxon 'Mallard' or 'Bat' resolve to multiple nodes"
},
This is the function that I built
componentDidMount = () => {
console.log("STATE", this.state);
if(this.props.response.articles.error == undefined){
if(this.props.response.articles.error.type === "NameResolutionException"){
Alert.alert("Please Refine Search or Specify Taxon.")
.then(()=>this.props.navigation.navigate("Home"));
}
}
};
The if statements will never hit despite the fact that the json file will the error object in it.
The expected output is that the error will be caught and then the app will go back to its the home screen but instead the app will fail because certain components will be missing.
Your code seems not correct, you are saying if error is undefined then comparing for a string value. This is contradictory.
It should be something like this, considering error is an object not an array
componentDidMount = () => {
console.log("STATE", this.state);
if(this.props.response.articles.error && this.props.response.articles.error.type){ // considering error object may or may not be present
if(this.props.response.articles.error.type === "NameResolutionException"){
Alert.alert("Please Refine Search or Specify Taxon.")
.then(()=>this.props.navigation.navigate("Home"));
}
else if (this.props.response.articles.error.type === "OtherPossibleStringException"){
}
else {
//Any unhandled error case
}
}
};

Categories

Resources