React-Native async Array.map = undefined is not an Object - javascript

I try to create my first hybrid App with ReactNative. I have an issue with my Array.map…
class HomeScreen extends React.Component {
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: {}
};
async getPokemonFromApiAsync() {
try {
this.setState({isLoading: true});
let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
return this.setState({
isLoading: false,
data: await response.json()
});
} catch (error) {
console.error(error);
}
(...)
componentWillMount() {
this.getPokemonFromApiAsync()
}
(...)
result = (result = this.state.data.results) => {
console.log('test', this.state.data);
return (
<View>
(...)
result.map( (item, index) => {
(...)
}
</View>
)
}
}
I don't understand, why my function getPokemonFromApiAsync is empty. iOS Simulator returns a TypeError: Undefined is not an object (evaluating 'result.map')
And when adding a constructor like:
constructor(props) {
super(props)
this.getPokemonFromApiAsync = This.getPokemonFromApiAsync.bind(this)
}
I have an many errors in console:
Warning: Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the %s component., setState, HomeScreen
For me, it's normal…
What is a good lifecycle for an asynchronous Http request?

Best way using axios library github link
npm install axios
Finally, weekly downloads are more than 4,000,000+ Github Starts 50,000+

Your error is caused by how you have set up your initial data in state.
You have set it up as:
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: {} // <- here you define it as an object, with no parameters
};
You should be setting it as an object with a results parameter`. So your initial state should look like
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: { results: [] } // <- here you should define the results inside the object
};
The reason you are getting the error:
TypeError: Undefined is not an object (evaluating 'result.map')
Is because on the initial render, before your fetch response has come back, it is trying to map over this.state.data.results which doesn't exist. You need to make sure that there is an initial value for results in the state.
That should stop the initial error, however you will have to make sure that what you are saving into state for data is also an array, otherwise you will continue to get the same error.
componentWillMount has been deprecated and you should be using componentDidMount.
Also as you are calling an async function inside you componentWillMount you should refactor it in the following way:
async componentDidMount() {
await this.getPokemonFromApiAsync()
}
So that the mounting doesn't occur until the fetch request has been completed.
I would also refactor your getPokemonFromApiAsync so that you get the response.json() before trying to set it into state. You also don't need the return statement as this.setState doesn't return anything.
async getPokemonFromApiAsync() {
try {
this.setState({isLoading: true});
let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
let data = await response.json(); // get the data
this.setState({
isLoading: false,
data: data // now set it to state
});
} catch (error) {
console.error(error);
}
Snack:
Here is a very simple snack showing the code working https://snack.expo.io/#andypandy/pokemon-fetch
Code for snack:
export default class App extends React.Component {
state = {
isLoading: false,
captured: false,
wished: false,
exchanged: false,
data: { results: [] } // <- here you should define the results inside the object
};
getPokemonFromApiAsync = async () => {
try {
this.setState({ isLoading: true });
let response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20');
let data = await response.json(); // get the data
this.setState({
isLoading: false,
data: data // now set it to state
});
} catch (error) {
console.error(error);
}
}
async componentDidMount () {
await this.getPokemonFromApiAsync();
}
render () {
return (
<View style={styles.container}>
{this.state.data.results.map(item => <Text>{item.name}</Text>)}
</View>
);
}
}

A better way is to implement your state values when your promise is resolved using "then".
let response = fetch('https://pokeapi.co/api/v2/pokemon/?limit=0&offset=20')
.then((response) => {
this.setState({
isLoading: false,
data: response.json()
});
});
Maybe you can process your data (result.map) in the Promise callback and directly insert the result in your component.
And by the way, XHR calls are generally processed in the componentDidMount method.

The reason you are getting the error TypeError: Undefined is not an object (evaluating 'result.map') is that you are getting the result from this.state.data.results, but because data is async, at the first time it renders, data is {} (because you set it in the state), so data.result is undefined and you can't use .map() in a undefined.
To solve this, you can check if data.result is not undefined, before render it.
return (
<View>
(...)
result && result.map( (item, index) => {
(...)
}
</View>
)

Related

Rendering data, fetched from an API in react-native

I am using React-Native and am having issues just getting data to render from an API, into the render function. I'm running node JS and express on one end to pull some data from a SQL database. This returns JSON that looks like this:
{"routines":[{"routine_id":1,"name":"Morning Routine","start_time":"2020-03-09T14:24:38.000Z","end_time":"2020-03-09T15:24:44.000Z","is_approved":0}]}
I want to loop through the routines key and print out each routine as components in React. I don't really care about what type of component that gets used, I just want to get the data. I've tried a few methods:
Method 1: Using componentDidMount with fetch:
constructor() {
super();
this.state = { routines: {} }
}
componentDidMount() {
fetch('http://localhost:3000/routines')
.then((response) => response.json())
.then((responseJson) => {
return responseJson;
})
.then( routines => {
this.setState({routines: routines});
})
.catch( error => {
console.error(error);
});
}
render() {
console.log(this.state)
render of this.state logs an empty object, despite the then(routines portion of the code returning the correct data.
Method 2: Putting everything in componentDidMount
async componentDidMount() {
const response = await fetch("http://localhost:3000/routines")
const json = await response.json()
console.log('json');
console.log(json);
const routines = json.routines
this.setState({routines})
}
Again, logging the state in render produces nothing while logging the json that gets returned from componentDidMount does return valid data.
Inside the render method i've also tried:
const { routines } = this.state;
And routines comes up as undefined.
Method 3: Directly calling a function to set the state.
constructor() {
super();
this.state = { routines: this.fetchData() }
}
This ends up returning some weird data:
{"routines": {"_40": 0, "_55": null, "_65": 0, "_72": null}}
I'm assuming it's because react native does not want me to do this.
I just want a simple way to fetch data from an API and display that data in render. I've gone through about four tutorials and all of them end up with undefined or objects set as the default value in the constructor in the render method. Am I going crazy? It feels like this is somehow impossible..?
You do everything right, just use state in render and you will see updates.
constructor() {
super();
this.state = { routines: [] }
}
render() {
const { routines } = this.state
return (
<View>
{routines.map(item => <Text>{item.name}</Text>)}
</View>
)
}
Since fetch is an async task the data this.setState({routines}) get's set after render() is executed. You can execute this.forceUpdate() after setting this.setState({routines}). This will re-execute render() when the data is set.
See: https://reactjs.org/docs/react-component.html#forceupdate
However, debugging mode can also be the culprit.
its may be because fetch call is async ,and your render method may try to use it before its loaded by the api call,
so your componentDidMount should be like
componentDidMount() {
this.setState({routines:null})
//fire an api call
fetch('http://localhost:3000/routines')
.then((response) => response.json())
.then((responseJson) => {
return responseJson;
})
.then( routines => {
this.setState({routines: routines});
})
.catch( error => {
console.error(error);
});
}
now inside your render function you should first confirm that routines is not null and have some valid values like
render(){
if(this.state.routines !==null){
//your code to render component
}else{
//your loading or error message
}
}

Why am I getting 2 times data null and data?

I am getting two time data null and data, what is my problem? And, why should I write two time data? Is it problem with json? Can anybody help me?
Contex.js
class ProviderWrapper extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
isLoading: false
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(URL + JSON_PATH)
.then(response => response.json())
.then(data => this.setState({ data, isLoading: false }));
}
render() {
const { children } = this.props;
return <Context.Provider value={this.state}>{children}</Context.Provider>;
}
}
test.js
import React, { Component } from "react";
import { Ctx } from "../Context/Context";
class Menu extends Component {
static contextType = Ctx;
render() {
const { data } = this.context;
console.log("data",data)
return (
<MenuWrapper>
{data && data.name}
</MenuWrapper>
);
}
}
data in ProviderWrapper starts out null and you don't start the fetch until componentDidMount, so data will be null for at least one call to render. You haven't shown what Menu and ProviderWrapper are both in, but Menu's render will be called whenever it needs to render, regardless of whether the fetch is done. It's not at all surprising that it does that at least once, and twice doesn't seem odd either.
Menu needs to be able to handle it when data is null (which it already seems to, so that's good).
A couple of side notes:
It's not the problem, but you're falling prey to a footgun in the fetch API: You need to check ok before calling json, details on my anemic little blog.
You're not handling errors at all. If the fetch fails for whatever reason, your ProviderWrapper is just left in the loading state forever. You need to handle errors.
Here's what that fetch call should look like:
fetch(URL + JSON_PATH)
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
response.json();
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => {
// ...handle/show error here and clear the loading state...
});
(In my projects, I have a wrapper for fetch so I don't have to do that every time. Making HTTP errors fulfillments rather than rejections was a major mistake in the API.)

React TypeError: this.setState is not a function

currently I'm writing an app in react that works with .NET Core web api and I'm getting:
TypeError: this.setState is not a function
I will explain when I get this error exactly in last paragraph
Now the view is intended to work as follows, on a page I have a table with data seeded from an array of JSON data and couple of inputs next to it. When I click on a table row it will put row data into inputs and I can edit the data from here. Two functions are handling this, first: editData is sending POST request to api and then it modify edited object in database. After this request is done second function: getData shall run and refresh a table with new, edited data. Here is how it looks:
Constructor:
class SomeClass extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
someArray: [],
tableData1: "",
tableData2: "",
validationSummary: [],
isSubmitting: false
}
this.handleChange = this.handleChange.bind(this); //function to handle change on inputs
this.getData = this.getData.bind(this);
this.editData= this.editData.bind(this);
}
Function for getting data:
async getData() {
return fetch("http://localhost:5000/api/somecontroller/getroute")
.then(response => response.json())
.then(data => {
this.setState({
someArray: data,
tableData1: "",
tableData2: "", //clear inputs
})
})
}
And finally edit object function:
async editData() {
var validationSummary = []
if (this.state.tableData1 !== "" && this.state.tableData2 !== "") {
this.setState = {
validationSummary: [], //clear summary
isSubmitting: true
}
let response = await fetch('http://localhost:5000/api/somecontroller/editroute', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
data1: tableData1,
data2: tableData2
})
});
if (response.status !== 200) {
validationSummary.push("Something went wrong")
this.setState = {
validationSummary: validationSummary,
isSubmitting: false
}
}
else {
await this.getData()
}
}
else {
validationSummary.push("Inputs cannot be empty")
this.setState = {
validationSummary: validationSummary,
isSubmitting: false
}
}
}
The problem is that whenever I edit some data and submit it to send request to API, website stops and I get the before mentioned error on this.setState line in the getData function:
async getData() {
return fetch("http://localhost:5000/api/somecontroller/getroute")
.then(response => response.json())
.then(data => {
this.setState({ //getting error here
I've read couple other questions similiar to this but everytime the solution was to bind function to this in constructor. However as you can see in my code I have binded both getData and editData functions to this. I probably have to mention that when I REMOVE all this.setState references from editData function, page is working properly and I'm no longer getting any error. I'm pretty new to react so I'm curious as to why I'm getting this error as it is and how can I fix this without removing this.setState references from editData function, because I need them for displaying error messages.
UPDATE
amrs-tech's answer fixed my problem, in editData changing this.setState = {...} to this.setState({...}) made it work. Hope it will be useful for someone in future!
if (this.setState.tableData1 !== "" && this.setState.tableData2 !== "")
this is incorrect. It should be like:
if (this.state.tableData1 !== "" && this.state.tableData2 !== "")
Your editData should be like:
async editData() {
var validationSummary = []
if (this.state.tableData1 !== "" && this.state.tableData2 !== "") {
this.setState ({
validationSummary: [], //clear summary
isSubmitting: true
})
let response = await fetch('http://localhost:5000/api/somecontroller/editroute', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
data1: tableData1,
data2: tableData2
})
});
if (response.status !== 200) {
validationSummary.push("Something went wrong")
this.setState ({
validationSummary: validationSummary,
isSubmitting: false
})
}
else {
await this.getData()
}
}
else {
validationSummary.push("Inputs cannot be empty")
this.setState ({
validationSummary: validationSummary,
isSubmitting: false
})
}
}
Hope it works. You can refer for some details about React state and lifecycle - here
setState is a function, also state and props are immutable.
// this.state.tableData1
this.setState.tableData1
// this.setState({...})
// and either way its should considered immutable
this.setState = {
validationSummary: [], //clear summary
isSubmitting: true
};
// also this is redundant because props are immutable.
this.props = props;
another quick way to fix this problem is to keep 'this' as variable
async getData() {
var scope = this;
return fetch("http://localhost:5000/api/somecontroller/getroute")
.then(response => response.json())
.then(data => {
scope.setState({ // scope replace 'this' here

This.state property is considered "undefined"

I'm fetching data from my backend to my frontend. After I invoke
let data = response.json(), I then invoke const bartData = Object.entries(data). So, I'm creating an array that holds the key/value pairs of my original object. I then set the state of my component this.setState({allStations: bartData}), where the property allStations: []. This is where the problem comes up- I want visual confirmation that I'm geting the right data and manipulate it the way I want to so I invoke console.log(this.state.allStations[0]) and it gives me the correct contents but when I go further console.log(this.state.allStations[0][0], I get an error that states
this.state.allStations[0] is undefined
Why?
Also, I get that I'm putting an array inside of an array, which is why I was surprised that console.log(this.state.allStations[0])gave me the contents of the original array. Picture of console.log(this.state.allStations) this.state.allStations
constructor(){
super(props);
this.state = {
allStations: []
}
}
async getAllStations(){
try{
const response = await fetch(`/base-station-routes`);
let data = await response.json();
// console.log(response);
// let test = JSON.parse(bartData);
// console.log(test)
const bartData = Object.entries(data);
// console.log(bartData[0][0]) works
this.setState({
allStations: bartData
})
}catch(e){
console.log(`Error: ${e}`)
}
}
render(){
console.log(this.state.allStations[0]);
return( more stuff )
}
[1]: https://i.stack.imgur.com/hQFeo.png
In render function before console.log(this.state.allStations[0]) you should check the state value.
Render function executes before fetching data from backend, my suggestion to do this
if(this.state.allStations) && console.log(this.state.allStations[0])
Do a conditional render to prevent it from showing the array before response has been sent.
Add something like:
constructor(){
super(props);
this.state = {
isLoading: true,
allStations: []
}
}
You need to use React Lifecycles and stick Fetch inside:
componentWillMount(){
fetch('/base-station-routes') // Already Async
.then(res => res.json()) // Convert response to JSON
.then(res => this.setState({ isLoading: false, allStations: res})) // you can call it bartData, but I like to stick with res
.catch(err => { //.catch() handles errors
console.error(err)
})
}
and then in your Render:
render(){
return(
<div>
{this.state.isLoading ? <span>Still Loading</span> : // do a map here over your data}
</div>
)
}
This prevents doing anything with the data before your response is there.

React. Why this.state does not updates correctly?

So, I try to run the function by condition: if I got an Error in the catch method.
To do this, I implement the this.state.loginError in component state, that will change on true if we got an Error. So, and after error - the this.state.loginError comese back with true (and this is also I saw in console.log), but after state changes - my function loginError(target) does not want to start anyway.
See my code and logs below, please:
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
loginError: false,
}
}
handleSubmit = (e) => {
e.preventDefault();
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
this.setState({
loginError: true
});
console.log(this.state.loginError); // gives `true`
});
if (this.state.loginError) {
console.log('Error!') //does not work
loginError(target);
}
};
Because axios.post is asyc function, and at first fires your if condition, and after .catch hook. For fix this try to replace your condition in other place, for example in componentDidUpdate method.
componentDidUpdate() {
if (this.state.loginError) {
console.log('Error!') //does not work
loginError(target);
this.setState({ loginError: false });
}
}
Check this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_functio
You are basically trying to check the state when the error is still not caught, and hence the state has not changed.
If you move your code to the render method you will see that it will works since it will re-render after the state changes. Or you get the state change withing the componentDidUpdate method.
Why don't you try Promises instead, which is very clear and simple way around.
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
loginError: false,
}
}
handleSubmit = (e) => {
e.preventDefault();
return new Promise(resolve, reject){
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
reject(err);
})
.then(result => resolve(result));
}
//Else where in Your component use this promise, where ever you call it..
handleSubmit().then(// success code).error(// error code)
};
Because axios.post returns a promise, all code you'll write after it will be executed before the .then() or .catch() statements. If your need is to call loginError() function when the request fails you can call it in .catch statement :
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
loginError(target);
});
If you need your function to be executed after updating the state you can use the setState callback (second argument) :
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
this.setState({ loginError: true }, () => { loginError(target); })
});

Categories