React Native state isn't changing - javascript

I'm making a Ajax request to a Json file that return some movies.
state = { movies: [] };
componentWillMount()
{
this.getMovies();
}
/*
Make an ajax call and put the results in the movies array
*/
getMovies()
{
axios.get('https://pastebin.com/raw/FF6Vec6B')
.then(response => this.setState({ movies: response.data }));
}
/*
Render every movie as a button
*/
renderMovies()
{
const { navigate } = this.props.navigation;
return this.state.movies.map(movie =>
<ListItem key={ movie.title }
title={ movie.title }
icon={{ name: 'home' }}
onPress={() =>
navigate('Details', { title: movie.title, release: movie.releaseYear })
}
/>
);
}
render() {
return(
<List>
{ this.renderMovies() }
</List>
);
}
The error I get is the following: this.state.map is not a function. This is because movies is still empty.
When I console.log response.data it returns all the rows from the JSON file. So the problem is most likely in this line:
.then(response => this.setState({ movies: response.data }));
Does someone know what's wrong?

You put initial state in the wrong place. Do this instead:
constructor(props) {
super(props);
this.state = { movies: [] };
}
From document:
In general, you should initialize state in the constructor, and then
call setState when you want to change it.

Update you ajax request as following:
/*
Make an ajax call and put the results in the movies array
*/
getMovies()
{
let self = this;
axios.get('https://pastebin.com/raw/FF6Vec6B')
.then(response => self.setState({ movies: response.data }));
}
Also, you can bind your function inside constructor as:
constructor(props){
super(props);
this.getMovies = this.getMovies.bind(this);
}

Related

How to correctly use componentDidMount and componentDidUpdate in React.js?

I have a problem. I want to search for an index based on a url. Everything is sent to the components as it should, but there is an error after loading:
Cannot read property 'indexOf' of undefined
Data sent from JSON is sure to be transmitted and received correctly and is correctly assigned. The problem is most likely caused by badly applied 'componentDidMount' and 'componentDidUpdate'. How should it look correctly?
The data sent based on the URL of the page is 'this.props.brand'
Code:
class CarPage extends Component {
state = {
isLoading: true,
carData: [],
id: null
}
findMyIndex = () => {
this.setState({
id: this.carData.indexOf(this.props.brand),
})
}
componentDidUpdate() {
this.findMyIndex()
}
componentDidMount() {
fetch("/data.json")
.then(response => response.json())
.then(data => {
this.setState({
carData: data,
isLoading: false,
})
})
}
render() {
return (
<>
{!this.state.isLoading && (
<p>{this.state.carData[this.state.id].model}</p>
)}
</>
);
}
}
export default CarPage;
You don't need componentDidUpdate lifecycle method at all. You can do it like this:
class CarPage extends Component {
state = {
isLoading: true,
carData: [],
id: null
}
findMyIndex = () => {
return this.state.carData.map(el => el.brand).indexOf(this.props.brand);
}
componentDidMount() {
fetch("/data.json")
.then(response => response.json())
.then(data => {
this.setState({
carData: data,
isLoading: false,
})
})
}
render() {
return (
<>
{!this.state.isLoading && (
<p>{this.state.carData[this.findMyIndex(this.props.brand)].model}</p>
)}
</>
);
}
}
export default CarPage;
It seems that findMyIndex returns -1 and this.state.carData[this.state.id] is equal to undefined. Check if CarData indeed has a this.props.brand entry.

Render() state element is initially empty until api call

I have a class component which should display some list values after an API call, in my render function I call a function to populate some other state list (with specific object properties from the fetched list), the problem is that in the render call, the state values are initially empty and as such the component I return is also just empty.
I've tried using componentDidUpdate() but I dont have much of an idea on how to go about using it, it usually gives me an infinite loop.
Here is my relevant code:
class AdminSales extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
data: [],
};
}
componentDidMount() {
this.fetchData();
}
fetchData() {
fetch("/api/items")
.then((res) => res.json())
.then((items) => this.setState({ items: items }));
}
componentDidUpdate(prevState) {
if (JSON.stringify(prevState.items) == JSON.stringify(this.state.items)) {
// Do nothing
} else {
// This gives infinite loop ...
// this.fetchData();
}
}
populateData() {
this.state.items.forEach(function (item) {
this.state.data.push({
name: item.name,
value: item.quantity,
});
}, this);
}
render() {
// Output shown line: 65
console.log(this.state);
this.populateData();
const { data } = this.state;
return ( ... );
}
}
export default AdminSales;
Any help will be much appreciated.
First of all there are multiple issues in your code
Updating/Mutating state in render
Instead of updating the state using setState, updating the state inplace in populateData method
Also, as #Drew mentioned we don't have to duplicate items into data instead we can store only the required info in the state once after getting the response from the API.
In the meantime while waiting for the response if you want to show a loading info you can do that as well.
Below is the example covering all those points mentioned above.
const mockAPI = () => {
return new Promise((resolve) => setTimeout(() => {
resolve([{id:1, name: "ABC", quantity: 1}, {id: 2, name: "DEF", quantity: 5}, {id: 3, name: "XYZ", quantity: 9}])
}, 500));
}
class AdminSales extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
loading: true
};
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
mockAPI()
.then((res) => {
this.populateData(res);
});
}
populateData = (data) => {
this.setState({
items: data.map(({name, quantity}) => ({
name,
value: quantity
})),
loading: false
})
}
render() {
//console.log(this.state);
const { items, loading } = this.state;
return loading ? <p>Loading...</p> :
items.map(item => (
<div>{item.name}: {item.value}</div>
));
}
}
ReactDOM.render(<AdminSales />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Note: For simplicity I've mocked the backend API with simple setTimeout.

how come my array is not being rendered on my react page?

Can someone help me out and tell me why it is not rendering my array object. The data is being fetched from my Mongoose database which gets printed on the console, but how come it is not rendering on the page.
Thanks!
class produce extends Component {
constructor() {
super();
this.state = {
loading: true,
fruits: []
};
}
componentDidMount() {
fetch('http://localhos:2000/api/fruits')
.then(response => response.json())
.then(produce => {
this.setState({ produce });
console.log(produce);
})
.catch(error => console.log(error));
}
render() {
return (
<div>
<ul>
this text above shows
{this.state.fruit.map(d => (
<li key={d.id}>{d.fruit}</li>
))}
</ul>
</div>
);
}
}
When you call the setState function with the parameter { produced }, you are creating the following state.
{
loading: true,
fruit: [],
produced: { data: [/* fruits data */] }
}
To avoid that, you need to access the property data in the API response and then assign it to the fruit property in your state.
The solution is changing the setStatecall to this.
this.setState({ fruit: produce.data })

Cannot call the first element of a JSON object

I'm trying to access the first object from data[]. Then, grab the keys using Object.keys() but it gives me this error:
"TypeError: Cannot convert undefined or null to object".
I need the output to be an array of the keys.
import React, { Component } from 'react';
class CodecChart extends Component {
constructor(props) {
super(props);
this.state = {
post: [],
isLoaded: false,
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(post => {this.setState({ post: post })
})
}
render() {
const data = this.state.post;
// cannot reach the first object of data[]
var keys = Object.keys(data[0]);
return (
<div>
//output should be an array of the keys
<h5>{keys}</h5>
</div>
)
}
}
export default CodecChart;
The first time you try to access data[0], it's still empty:
this.state = {
post: [],
isLoaded: false,
}
and const data = this.state.post; means that data[0] is undefined.
it's only after the component is mounted, and the state is set correctly that data[0] is defined (or not, depending on what the API returns).
I found a way for it to work by adding another "then" so it can set the "keys" state right after the "posts" state was set. But I wonder if there is another way to make it more elegant. Thank you for trying to help.
constructor(props) {
super(props);
this.state = {
posts: [],
isLoaded: false,
keys: []
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(posts => {
this.setState({ posts: posts })
})
.then(_ => { this.setState({ keys: Object.keys(this.state.posts[0]) }) })
}
render() {
const keys = this.state.keys;
return (
<div>
<h5>{keys}</h5>
</div>
)
}

Console.log() shows undefined before getting data

I seem to have a lifecycle hook issue that I can't seem to solve.
export default class EditRevision extends Component {
state = {
data: [],
customColumns: []
}
componentWillMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
}
render() {
/* THIS IS THE CONSOLE.LOG() I AM REFERRING TO */
console.log(this.state.data.subscriptionRevisionDTOS)
return (
<div></div>
)
}
}
And this is my log upon rendering the component
https://i.gyazo.com/9dcf4d13b96cdd2c3527e36224df0004.png
It is undefined, then retrieves the data as i desire it to, then it gets undefined again.
Any suggestions on what causes this issue is much appreciated, thank you.
Replace this:
componentWillMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
with:
constructor(props){
super(props)
this.state = {
data: [],
customColumns: []
}
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
}
try to call axios in constructor or componentDidMount() (componentWillMount should not be used). the undefined result is caused by the async call. Looks like you have a lot of uncontrolled renders. try to add a shouldComponentUpdate function or convert your component in a PureComponent
Take a look at https://reactjs.org/docs/react-component.html
You have init the state with
state = {
data: [],
customColumns: []
}
Here this.state.data is empty array which did not have definition of
subscriptionRevisionDTOS that is why you are getting this.state.data.subscriptionRevisionDTOS undefined.
Meanwhile, your asyncaxios.get call is completed and this.state.data is updated with subscriptionRevisionDTOS.
As soon as state is updated render() called again and you are getting the proper value of this.state.data.subscriptionRevisionDTOS.
So below line will surely work.
state = {
data:{subscriptionRevisionDTOS:[]},
customColumns: []
}
export default class EditRevision extends Component {
state = {
data:{subscriptionRevisionDTOS:[]},
customColumns: []
}
componentDidMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' +
(this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
render() {
/* THIS IS THE CONSOLE.LOG() I AM REFERRING TO */
console.log(this.state.data.subscriptionRevisionDTOS)
return (
<div></div>
)
}
see this it should be like this

Categories

Resources