React - Can't render array of elements - javascript

I can't render an array of objects into my view using JSX. It does not load on the screen.
I've been learning React and I can render an array of strings, but not the array of objects.
Here is my component:
import React, { Component } from "react";
export default class PokedexGridComponent extends Component {
constructor(props) {
super(props);
console.log(props);
this.state = {
pokemons: [],
all: []
};
}
componentDidMount() {
this.getPokemons();
}
render() {
return (
<div>
<input
className="btn btn-success btn-sm mb-5"
type="button"
onClick={this.getPokemons}
value="Buscar Pokemons"
/>
<div>
{this.state.all.map(data => {
return <li key={data.key}>{data.name}</li>;
})}
</div>
</div>
);
}
getPokemons = () => {
var pokemon = [];
fetch("https://pokeapi.co/api/v2/pokemon?offset=20&limit=964")
.then(data => {
return data.json();
})
.then(data => {
data["results"].forEach(data => {
pokemon.push(data.name.charAt(0).toUpperCase() + data.name.slice(1));
});
this.setState({ pokemons: pokemon });
return this.state.pokemons;
})
.then(data => {
var tmp = [];
this.state.pokemons.forEach((data, idx) => {
fetch(`https://pokeapi.co/api/v2/pokemon/${data.toLowerCase()}`)
.then(data => {
return data.json();
})
.then(data => {
tmp.push({
name: data.name,
image: data.sprites.front_default,
key: idx
});
});
});
this.setState({ all: tmp });
console.log(this.state.all);
});
};
}
The console returns the array of objects but can't map it on render method.
May anyone help me?

The setState method is async so if you need to do something after updated the state you would need to use the second parameter that is a function that will be executed after updating the state.
this.setState({ pokemons: pokemon }, function(){
//perform what you need with the updated
})
Another issue you are having is that you are updating before the requests have arrived. You can collect all your promises and apply them Promise.all:
const requests = []
pokemons.forEach((data, idx) => {
const request = fetch(`https://pokeapi.co/api/v2/pokemon/${data.toLowerCase()}`)
.then((data) => {
return data.json();
})
requests.push(request)
})
const tmp = [];
Promise.all(request).then((arrayOfResults) => {
//Here you have the responses to iterate
arrayOfResults.forEach((data) => {
tmp.push({
name: data.name,
image: data.sprites.front_default,
key: idx
})
})
this.setState({all: tmp}, function(){
//here you can see the state after update
})
})

Works with Promise.all, thank you #SergioEscudero and #Vencovsky.
Here is the modified code:
import React, { Component } from 'react'
export default class PokedexGridComponent extends Component {
constructor(props) {
super(props);
this.state = {
pokemons: [],
pokeComplete: []
}
}
componentDidMount() {
this.getPokemons();
}
render() {
return (
<div>
<input className="btn btn-success btn-sm mb-5" type="button" onClick={this.getPokemons} value="Buscar Pokemons" />
<div>
{
(this.state.pokeComplete.map((data) => {
return (<li>{data.name} | {data.img}</li>)
}))
}
</div>
</div>
)
}
getPokemons = () => {
var pokemons = [];
fetch("https://pokeapi.co/api/v2/pokemon?offset=20&limit=964")
.then((data) => {
return data.json();
})
.then((data) => {
data["results"].forEach((data) => {
pokemons.push(data.name)
})
return pokemons;
}).then((data) => {
var arrayReq = [];
data.forEach((x) => {
var req = fetch(`https://pokeapi.co/api/v2/pokemon/${x.toLowerCase()}`)
.then((data) => {
return data.json();
})
arrayReq.push(req);
})
var tmp = [];
Promise.all(arrayReq)
.then((e) => {
e.forEach((x) => {
tmp.push({
name: x.name,
img: x.sprites.front_default
})
})
})
.then(() => {
this.setState({ pokeComplete: tmp });
})
})
}
}

Related

Correct way to fetch through array

In the below compoenent, the function is neverending. Can someone tell me what to fix so that in the end the beers array in the state has 5 names?
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
didError: false
};
this.getBeerInfo = this.getBeerInfo.bind(this);
}
render() {
return (
...
}
getBeerInfo() {
let beerArr = [1,2,3,4,5];
this.props.beerArr.map(id => {
fetch(`https://api.punkapi.com/v2/beers/${id}`)
.then(res => res.json())
.then(json => {
this.setState(state => {
const beers = state.beers.concat(json[0].name);
return {
beers
};
});
})
.catch(err => {
this.setState({
didError : true
});
});
})
}
}
Well your code should be somethings like this ..
import React from 'react';
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
didError: false
};
this.getBeerInfo = this.getBeerInfo.bind(this);
}
render() {
return (
<div>{this.state.beers}</div>
)
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo() {
let beerArr = [1,2,3,4,5];
beerArr.map(id => {
fetch(`https://api.punkapi.com/v2/beers/${id}`)
.then(res => res.json())
.then(json => {
this.setState({
//const beers = state.beers.concat(json[0].name);
//return {
//beers
//};
beers: this.state.beers.concat(json[0].name)
});
console.log('well at least this works')
})
.catch(err => {
this.setState({
didError : true
});
});
})
}
}
It is advised that you use the componentDidMount() lifecycle method for the fetch api and add what #atahnksy said.
When you are using setState, you can try this:
this.setState({ beers: [...this.state.beers, json[0].name])
This might fix your problem.
You can improve the render method using a combination of ternary operator(to display appropriate message when it cannot reach the server), format with map and ordered list to get something like this :
render() {
return (
<div><ol>{this.state.beers.length!==0 ? this.state.beers.map((beer)=><li>{beer}</li>) :"Could not retrieve any bears. Try again/ensure you can access the server/networtk"}</ol></div>
)
}

State array does not render properly in React.js

I am working on a hacker news clone I am trying to get the ids of the top stories from their api using axios in componentDidMount and then making another axios call to get the stories and push them in a state array but when I try to map over and render that array nothing shows up
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.state.posts.push(value)
})
.catch(err =>{
console.log(err)
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
<Header title="Hacker News" />
{this.state.posts.map( (element, index) => <Post key={element.data.id} serialNum={index} postTitle={element.data.title} postVotes={element.data.score} postAuthor={element.data.by} />) }
</div>
)
}
}
Try setting the state like this:
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({
posts: [value, ...this.state.posts]
})
})
.catch(err =>{
console.log(err)
})
})
This way you're using setState and appending every new value to the existing state.
As stated in the comments, don't use push for set state. In your code when you make the second request you must change the setState method to spread out the new value.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState(prevState => ({posts: [ value.data, ...prevState.posts]}))
})
.catch(err =>{
console.log("err");
console.log(err);
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
{this.state.posts && this.state.posts.map( (element, index) =>
<div key={element.id}>
{element.title}
</div>
)}
</div>
);
}
}
componentDidMount() is called after Render() only once. React doesn't know about the state changes unless you use setState().
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({posts: [value, ...this.state.posts]})
})
})
})
}
Use this.setState({posts : [value, ...this.state.posts]}) instead of this.state.posts.push(value). using ... (spread operator) appends the value to the original posts array.

Return promise from React Redux Thunk

I have the next React/Redux/Thunk code:
This is my call to API:
//api.js
export const categoriesFetchData = (page, parentOf) => {
return axios.get(
url +
'categories/' +
'?parent=' +
parentOf +
'&limit=10' +
'&offset=' +
(page - 1) * 10,
);
};
This my action (I pretend to return an axios promise from this action):
//actions.js
export const subCategoriesFetchData = (page = 1, parentOf) => {
return dispatch => {
dispatch(oneCategoryLoading(true));
return api.categoriesFetchData(page, parentOf)
.then(response => {
dispatch(subCategoriesFetchDataSuccess(response.data.results));
dispatch(oneCategoryLoading(false));
})
.catch(error => {
console.log(error);
});
};
};
And in my container:
const mapDispatchToProps = dispatch => {
return {
fetchOneCategory: slug => {
dispatch(fetchOneCategory(slug)).then(() => {
console.log('working');
});
},
};
};
But I get this error:
Uncaught TypeError: Cannot read property 'then' of undefined
What is wrong and how to return a promise in the container?
Thanks for your help.
Here's how I am approaching this.
First, there are a couple of changes you need to do to your subCategoriesFetchData function:
export const subCategoriesFetchData = (page = 1, parentOf) => {
return dispatch => {
dispatch(oneCategoryLoading(true));
// That's the key thing. Return a new promise, then ma
return new Promise((resolve, reject) => {
api.categoriesFetchData(page, parentOf)
.then(response => {
dispatch(subCategoriesFetchDataSuccess(response.data.results));
dispatch(oneCategoryLoading(false));
resolve(response); // Resolve it with whatever you need
})
.catch(error => {
console.log(error);
reject(error); // And it's good practice to reject it on error (optional)
});
});
};
};
Then, here's how you can do the trick with mapDispatchToProps and then chaining .then()s:
import React from 'react';
import { connect } from 'react-redux';
import { subCategoriesFetchData } from './wherever-it-is';
class MyComponent extends React.Component {
componentDidMount() {
this.props.subCategoriesFetchData()
.then( () => { console.log('it works'); })
.catch( () => { console.log('on error'); });
}
render() {
return ( <p>Whatever</p> );
}
}
const mapDispatchToProps = { subCategoriesFetchData };
connect(null, mapDispatchToProps)(MyComponent);
Sorry, i will answer my own question:
i change this:
const mapDispatchToProps = dispatch => {
return {
fetchOneCategory: slug => {
dispatch(fetchOneCategory(slug)).then(() => {
console.log('working');
});
},
};
};
to
const mapDispatchToProps = dispatch => {
return {
fetchOneCategory: slug => {
return dispatch(fetchOneCategory(slug))
},
};
};
and now i can make this:
import React from 'react';
import { connect } from 'react-redux';
class MyComponent extends React.Component {
componentDidMount() {
this.props.fetchOneCategory()
.then( () => { console.log('it works'); })
.catch( () => { console.log('on error'); });
}
render() {
return ( <p>Whatever</p> );
}
}
thanks for your help!
in your container, you don't need
.then(() => {
console.log('working');
});
dispatch(fetchOneCategory(slug)) doesn't return a promise, there is no then to be read

Delete request with axios - React

I'm trying to create small app based on Json server package which will help me to remember movies I want to watch when I have free time, want to learn React and Axios so I'm doing it with these technologies , Idea is when I click on add movie button - movie will be added to Json database,
when click on delete - particular movie will be deleted
and when click on the list - I will be able to edit text,
Delete works if I do something like http://localhost:3000/movies/1, to show what id should it delete, but is there any way to set it? To delete the list connected to button I'm clicking at? something like http://localhost:3000/movies/"id"? I will be grateful for any help as I totally don't have any idea how to move on with it
import React from 'react';
import ReactDom from 'react-dom';
import axios from 'axios';
import List from "./list.jsx";
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
name:'',
type:'',
description:'',
id:'',
movies: [],
}
}
handleChangeOne = e => {
this.setState({
name:e.target.value
})
}
handleChangeTwo = e => {
this.setState({
type:e.target.value
})
}
handleChangeThree = e => {
this.setState({
description:e.target.value
})
}
handleSubmit = e => {
e.preventDefault()
const url = `http://localhost:3000/movies/`;
axios.post(url, {
name: this.state.name,
type: this.state.type,
description:this.state.description,
id:this.state.id
})
.then(res => {
// console.log(res);
// console.log(res.data);
this.setState({
movies:[this.state.name,this.state.type,this.state.description, this.state.id]
})
})
}
handleRemove = (e) => {
const id = this.state.id;
const url = `http://localhost:3000/movies/`;
// const id = document.querySelectorAll("li").props['data-id'];
e.preventDefault();
axios.delete(url + id)
.then(res => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
})
}
// editMovie = e => {
// const url = `http://localhost:3000/movies/`;
// e.preventDefault();
// const id = e.target.data("id");
// axios.put(url + id, {
// name: this.state.name,
// type: this.state.type,
// description:this.state.description,
// })
// .then(res => {
// console.log(res.data);
// })
// .catch((err) => {
// console.log(err);
// })
// }
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="movie" onChange={this.handleChangeOne}/>
<input type="text" placeholder="type of movie" onChange={this.handleChangeTwo}/>
<textarea cols={40} rows={5} placeholder="description of the movie" onChange={this.handleChangeThree}></textarea>
<input type="submit" value="Add movie"></input>
<List removeClick={this.handleRemove} editClick={this.editMovie}/>
</form>
)
}
}
export default Form
List:
import React from 'react';
import ReactDom from 'react-dom';
import axios from 'axios';
class List extends React.Component{
constructor(props){
super(props)
this.state = {
movies: [],
}
}
componentDidMount() {
const url = `http://localhost:3000/movies`;
console.log(url);
axios.get(url)
.then(res => {
console.log(res.data);
const movies = res.data;
this.setState({
movies: movies
})
})
.catch((err) => {
console.log(err);
})
}
// editMovie =(e) => {
// console.log("it works with edit!");
// if (typeof this.props.editClick === "function") {
// this.props.editClick(e)
// } else {
// console.log("Doesn't work with edit");
// }
// }
removeMovie =(e) => {
console.log("it works with remove!");
if (typeof this.props.removeClick === "function") {
this.props.removeClick(e)
} else {
console.log("Doesn't work with remove");
}
}
render(){
let movies = this.state.movies.map(e =>
<ul onClick={this.editMovie}>
<li data-id={e.id}>
{e.name}
</li>
<li data-id={e.id}>
{e.type}
</li>
<li data-id={e.id}>
{e.description}
</li>
<button type="submit" onClick={this.removeMovie}>Delete</button>
</ul>)
return(
<div>
{movies}
</div>
)
}
}
export default List;
Json part
{
"movies": [
{
"id": 1,
"name": "Kongi",
"type": "drama",
"description": "movie about monkey"
},
{
"id": 2,
"name": "Silent Hill",
"type": "thriller",
"description": "movie about monsters"
},
{
"name": "Harry potter",
"type": "fantasy",
"description": "movie about magic and glory",
"id": 3
}
]
}
You could pass the movie object to the removeMovie function in your List component and pass that to the this.props.removeClick function. You could then take the id of the movie to use for your request, and remove the movie from state if the DELETE request is successful.
Example
class Form extends React.Component {
handleRemove = movie => {
const url = `http://localhost:3000/movies/${movie.id}`;
axios
.delete(url)
.then(res => {
this.setState(previousState => {
return {
movies: previousState.movies.filter(m => m.id !== movie.id)
};
});
})
.catch(err => {
console.log(err);
});
};
// ...
}
class List extends React.Component {
removeMovie = (e, movie) => {
e.preventDefault();
if (this.props.removeClick) {
this.props.removeClick(movie);
}
};
// ...
render() {
return (
<div>
{this.state.movies.map(movie => (
<ul onClick={this.editMovie}>
<li data-id={movie.id}>{movie.name}</li>
<li data-id={movie.id}>{movie.type}</li>
<li data-id={movie.id}>{movie.description}</li>
<button type="submit" onClick={e => this.removeMovie(e, movie)}>
Delete
</button>
</ul>
))}
</div>
);
}
}
An simple example using hooks:
const URL = 'https://jsonplaceholder.typicode.com/users'
const Table = () => {
const [employees, setEmployees] = React.useState([])
React.useEffect(() => {
getData()
}, [])
const getData = async () => {
const response = await axios.get(URL)
setEmployees(response.data)
}
const removeData = (id) => {
axios.delete(`${URL}/${id}`).then(res => {
const del = employees.filter(employee => id !== employee.id)
setEmployees(del)
})
}
const renderHeader = () => {
let headerElement = ['id', 'name', 'email', 'phone', 'operation']
return headerElement.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
const renderBody = () => {
return employees && employees.map(({ id, name, email, phone }) => {
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{email}</td>
<td>{phone}</td>
<td className='opration'>
<button className='button' onClick={() => removeData(id)}>Delete</button>
</td>
</tr>
)
})
}
return (
<>
<h1 id='title'>React Table</h1>
<table id='employee'>
<thead>
<tr>{renderHeader()}</tr>
</thead>
<tbody>
{renderBody()}
</tbody>
</table>
</>
)
}
ReactDOM.render(<Table />, document.getElementById('root'));

React state updating but not rendering on child component

I know the state is updating because 1. the 'Loading...' is going away, I can console log this.state.images to see the array. However when the state updates and the loading goes the searchbar shows up but the Card's within CardList do not.
They do show up when I search for a correct string, but not before.
If I pass this.state.images to CardList they show up perfectly. However when I move to the filteredImages they only show up when filtered.
Any ideas? Thanks in advance.
class App extends Component {
constructor() {
super();
this.state = {
images:[],
searchfield: ''
}
}
getLabels = (image) => {
const AuthKey = key.key;
const res = axios.post(`https://vision.googleapis.com/v1/images:annotate?key=${AuthKey}`, {
requests: [
{
image:{
source:{
imageUri: `http://storage.googleapis.com/${image}`
}
},
features:[
{
type:"LABEL_DETECTION",
maxResults:10
}
]
}
]
});
res.then(function (response) {
const results = response.data.responses[0].labelAnnotations;
const ex = results.map(result => {
return result.description;
});
return ex;
});
return res;
};
componentDidMount() {
imageFiles.imageFiles.forEach(img => {
this.getLabels(img).then(result => {
const results = result.data.responses[0].labelAnnotations;
const labels = results.map(result => {
return result.description;
});
//Add new values to the state
this.setState({images:[...this.state.images, {img, labels}]});
});
})
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value});
}
render() {
const filteredImages = this.state.images.filter(image => {
return image.labels.includes(this.state.searchfield.toLowerCase());
});
// Create an array of objects to store the image path and the labels detected from Google Vision
if (this.state.images.length === 0) {
return <h1>Loading...</h1>
} else {
return (
<Grid className="App">
<SearchBox searchChange={this.onSearchChange}/>
<CardList images={filteredImages} />
</Grid>
)}
}
}
export default App;
class App extends Component {
constructor() {
super();
this.state = {
images:[],
searchfield: '',
filteredImages:[]
}
}
getLabels = (image) => {
const AuthKey = key.key;
const res = axios.post(`https://vision.googleapis.com/v1/images:annotate?key=${AuthKey}`, {
requests: [
{
image:{
source:{
imageUri: `http://storage.googleapis.com/${image}`
}
},
features:[
{
type:"LABEL_DETECTION",
maxResults:10
}
]
}
]
});
res.then(function (response) {
const results = response.data.responses[0].labelAnnotations;
const ex = results.map(result => {
return result.description;
});
return ex;
});
return res;
};
componentDidMount() {
imageFiles.imageFiles.forEach(img => {
this.getLabels(img).then(result => {
const results = result.data.responses[0].labelAnnotations;
const labels = results.map(result => {
return result.description;
});
//Add new values to the state
this.setState({images:[...this.state.images, {img, labels}]});
this.setState({filteredImages:[...this.state.images, {img, labels}]});
});
})
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value});
let filteredImages = this.state.images.filter(image => {
return image.labels.includes(this.state.searchfield.toLowerCase());
});
this.setState({filteredImages});
}
render() {
// Create an array of objects to store the image path and the labels detected from Google Vision
if (this.state.images.length === 0) {
return <h1>Loading...</h1>
} else {
return (
<Grid className="App">
<SearchBox searchChange={this.onSearchChange}/>
<CardList images={this.state.filteredImages} />
</Grid>
)}
}
}
export default App;

Categories