React - prop undefined - javascript

I'm currently learning to build apps with React (not using Redux yet). So far everything is working as expected except when it comes to load the data (via ajax). Let me refine what I mean.
I'm using the spotify API using the library "spotify-web-api-js".
So far, I have 2 components:
Search Field
Display the list of Artists
Of course everything is wired up on the App level.
App Level
class App extends Component {
constructor(props) {
super(props);
this.state = {
artists: []
}
this.searchArtists('Chris');
}
searchArtists(artist) {
api.searchArtists(artist, (err, data) => {
if (err) { console.log(err); };
this.setState({ artists: data }, () => {
console.log(this.state.artists);
});
});
}
render() {
const artistSearch = (artist) => { this.searchArtists(artist); };
return (
<div>
<Search onSearchTermChange={ artistSearch } />
<SearchResult artists={ this.state.artists.artists } />
</div>
);
}
};
Search
class Search extends Component {
constructor(props) {
super(props);
this.state = { artist: '' };
}
render() {
return (
<div className="search-bar">
<input
value={ this.state.artist }
onChange={ event => this.onInputChange(event.target.value) }
placeholder="Search by Artist" />
</div>
);
}
onInputChange(artist) {
this.setState({ artist });
this.props.onSearchTermChange(artist);
}
}
export default Search;
Search Results
const SearchResult = props => {
if (!props) {
console.log('loading');
}
return <li></li>;
}
THE PROBLEM
I'm trying to display the results in SearchResult, but it always returns undefined. I tried a settimeout but I know that's the not the solution to my problem.
Why would props return undefined? Even if the data seems to return fine.
I will continue digging for a simple answer and will Edit the question if I find something.
Thanks in advance!
EDIT:
So here is the response I get in the console:
{
artists: {
artists: {
href: 'sikshdksad',
items: [array],
limit: 20,
....
}
}
}
There are 2 artists, because this.state = { artists: [] } if I'm not wrong.

It looks like you're initially defining your artists state as an array:
constructor(props) {
super(props);
this.state = {
artists: []
}
this.searchArtists('Chris');
}
You eventually set that to whatever data returns in your return function. But when you reference the artists data to pass in as a prop, it looks like you're trying to access the artists value on the artists object:
<SearchResult artists={ this.state.artists.artists } />
Maybe just get rid of that last .artists?

Related

setState() With Object.keys() Through Fetch Requests In Parent Component

I am trying to set the state with data received from a fetch request in the parent component. I am receiving an array of objects that each have the following keys: ‘name’, ‘artist’, ‘url’, ‘cover’, ‘lrc’, and ‘theme’. I am using Object.keys() to map over the object data, but I am wondering how I can set the state in this way so as to have multiple objects with those six keys be stored in the state so my state will look like:
this.state = { data: [{ {…}, {…}, {…}, etc… }] }
One big issue is that my data - from a fetch request in the parent - is not rendering in this tempComp component. I am passing the data in as a prop (this.props.playlist). Why is the fetched data in the parent not rendering in the tempComp component, and how can I set state with multiple objects, as I attempted below with Object.keys()? Any advice is greatly appreciated.
import React, { Component } from 'react'
class tempComp extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
audio: [{
name: '',
artist: '',
url: '',
cover: '',
lrc: '',
theme: ''
}]
}
}
async componentDidMount() {
console.log('playlist in componentDidMount()', this.props.playlist) //<--- AudioPlayer data should be coming in here
var json = this.props.playlist;
var arr = [];
Object.keys(json).forEach(function (key) {
arr.push(json[key]);
});
arr.map(item => {
this.setState({
audio: [{
name: item.name,
artist: item.artist,
url: item.url,
cover: item.cover,
lrc: item.lrc,
theme: item.theme
}]
})
})
console.log(this.state.audio);
}
render() {
return (
<div>
</div>
)
}
}
export default tempComp
And here is the parent component, for clarification:
export default class PostContent extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
episodeData: [],
}
}
async componentDidMount(){
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
this.setState({
episodeData: jsonData, //this is working!...
id: id
});
console.log(this.state.episodeData) //...see?
}
render() {
return (
<Fragment>
<TempComp playlist={this.state.episodeData} />
<AudioPlayer playlist={this.state.episodeData} />
<Link id='home-link' to='/' activeClassName='active'>Homepage</Link>
{this.state.episodeData.map((item, i) => (
<div key={i} className="word-content">
<h2 className="show-title">{item.post_title}</h2>
<div className="episode-post-content">{item.post_content}</div>
</div>
))}
<Table data={this.state.data} />
<div className="bottom-link">
<Link id='home-link' to='/' activeClassName='active'>Homepage</Link>
</div>
</Fragment>
)
}
}
You've been a bit tricked here with the way setState works. It's an asynchronous method which means it's not going to run in tandem with your map. You could use a setTimeout or an await which I can see is possible given your componentDidMount is prefixed by async. However, if I can take the liberty, i'd recommend you make some small changes like so:
const json = {
apple: { yum: false },
peach: { yum: true }
};
const yummyness = Object.keys(json).map(key => {
return { yum: json[key].yum }
});
// yumminess will render: [{ yum: false }, { yum: true }]
this.setState({ yummyness });
What I've done here is a few things:
If data isn't changing, assign it to a const, you can learn more about that here
Map returns an array. This can be really handy such as now, so instead of pushing to your arr value, I've just returned an object to prevent you doing your second map.
Finally, as I mentioned earlier setState is asynchronous and a bit of a slippery sucker! So to avoid that I'm just letting the map do it's thing and THEN I've assigned it to yummyness.
Bonus Round: I've used fruit but I've left most of your naming the same. json is your props.playlist and yumminess is your arr
Hope this helped!

React axios request in ComponentDidMount not passing data as props to component

Hi guys I can't see my Error here hope someone can hlep...
This is my fetch Data class:
export default class Auftrag extends Component {
state = {
auftraege: "Test",
};
getAuftraege = () => {
axios.get("Auftraege/auftraege").then(e => {
this.setState({
auftraege: e.data,
});
console.log(e.data);
});
};
componentDidMount() {
this.getAuftraege();
}
render() {
return (
<>
<AuftragDisplay test={this.state.auftraege} ></AuftragDisplay>
</>
);
}
}
And this is my constructor in my Display class:
constructor(props) {
super(props);
console.log(props);
}
The axios Request is getting fired and I get the right data in my console. But It is not getting passed to my Component.
Hope someone knows whats wrong and can help me
SOLVED:
Thx to san I tried it and could solve the problem. I got the data passed but console.log() was called before the update so I got the old data. THX again
Your code looks fine. you can see below same code with different api as an example
class Auftrag extends Component {
state = {
auftraege: "Test",
};
getAuftraege = () => {
axios
.get("https://jsonplaceholder.typicode.com/posts/1")
.then(e => this.setState({auftraege: e.data}))
};
componentDidMount() {
this.getAuftraege();
}
render() {
return (
<>
<AuftragDisplay test={this.state.auftraege} ></AuftragDisplay>
</>
);
}
}
const AuftragDisplay = ({test}) =><h2>Hi--->{test.title}</h2>
Just put the state inside constructor of Auftrag class, I should work.

Map is not a function : {} JSON array

I just went from Vue to React and I'm a little lost on the iteration of an array.
With the same API, everything works with Vue but not with React.
Here is an example of an answer from my API:
{
"blade": {
"id":"1",
"key":"blade"
},
"sword": {
"id":"2",
"key":"sword"
}
}
I think the problem is that my API response returns an array but with the symbols {} and not []
If the problem comes from this, how can I solve it?
This is my current code:
class ItemSelection extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
};
}
componentDidMount() {
fetch('https://myapi.com/items.json')
.then(response => response.json())
.then(data => this.setState({ items: data }));
}
render() {
return (
<div>
{
this.state.items.map(item => (
<div>...</div>
))
}
</div>
);
}
}
The response is a Object.... you can use the function Object.values(items) to get a list of values and use the map function.
.then(data => this.setState({ items: Object.values(data) }));
I solved a similar problem with data = Array.from(data);
{} denotes a JSON object, not an array, a map object is also treated in a similar way. Therefore, you can access the elements of the object/map using as below
<div v-for="item in items">
<p><strong>Id:</strong> {{ item.id}}</p>
<p><strong>Key:</strong>{{ item.key}}</p>
</div>
for more information, you can refer https://v2.vuejs.org/v2/guide/list.html

Get movies of all genres

I am making a movie application with ReactJS and the TMDb API, I would like to get the movies by genres, and display them in my homepage, for that I created for example a method initHorrorMovies() who:
Performs an axios TDMb API request for movies of a kind
https://developers.themoviedb.org/3/discover/movie-discover
Changes the state horrorMoviesList with new data
The problem is that there are many genres, so I'm going to create as many functions and states as there are genres.
I was thinking of creating a movieList object that would contain the results of the tmdb query for each genre as well as the title of the genre, and then update a movieList state with that object.
Do you have any suggestions for me?
I tried this
class App extends Component {
constructor(props){
super(props);
this.state = {
movieListWithGenre:[],
}
}
componentWillMount() {
this.initGenreMovie();
}
initGenreMovie(){
axios.get(LINK_GENRES).then(function(response){
this.initListMovie(response.data.genres)
}.bind(this));
}
initListMovie(GenreList){
this.setState({ moviesList: this.state.moviesList.push(newMovies)});
GenreList.map((element) => {
axios.get(`${API_END_POINT}discover/movielanguage=en
&with_genres=${element.id}
&include_adult=false&append_to_response=images
&${API_KEY}`).then(function(response){
this.setState({movieListWithGenre:this.state.movieListWithGenre.
push(response.data.results)})
}.bind(this));
})
}
}
Edit
Hello, I allow myself to go back the post because I develop a solution that works, I am able to get the list of films sorted by genres using the TMDB API request.
My solution works but I have a lot of latency when launching the application because I think the procedure is heavy, performance is impaired.
Here is my code, could I have some tips to improve this code? I thank you in advance for answers.
class App extends Component {
constructor(props){
super(props);
this.state = {
defaultgenre:28,
movieListWithGenre:[],
genreList:[],
genreId:[],
genreTitle:[]
}
}
componentDidMount() {
this.initGenreMovie();
}
initGenreMovie(){
axios.get(`${LINK_GENRES}`).then(function(response){
this.initListMoviesWithGenre(response.data.genres)
}.bind(this));
}
initListMoviesWithGenre(genres){
genres.map((genre) => {
axios.get(`${API_END_POINT}${POPULAR_MOVIES_URL}&${API_KEY}`)
.then(function(response){
let movies = response.data.results.slice(0,14);
let titleGenre = genre.name;
let idGenre = genre.id;
this.setState({movieListWithGenre:[...this.state.movieListWithGenre, movies]});
this.setState({genreTitle:[...this.state.genreTitle, titleGenre]});
this.setState({genreId:[...this.state.genreId, idGenre ]});
}.bind(this));
})
}
render(){
const renderVideoListGenre = () => {
if(this.state.movieListWithGenre) {
return this.state.movieListWithGenre.map((element,index) => {
return (
<div className="list-video">
<Caroussel
key={element.name}
idGenre {this.state.genreId[index]}
movieList={element}
titleList={this.state.genreTitle[index]}
/>
</div>
)
})
}
}
return (
<div>
{renderVideoListGenre()}
</div>
)
}
export default App
Once you discovered all the genre ids you want you can begin making axios calls to
https://api.themoviedb.org/<GENRE_ID>/genre/movie/list?api_key=<API_KEY>&language=en-US
You can make a single function for all genres or split them up, but should likely be called in the constructor. Once your axios calls return, you can put the movies data into your state like so:
this.setState({ moviesList: this.state.moviesList.push(newMovies) });
The shape of your movie and moviesList object is up to you and the data returned by the API.

What is the correct way to add to an array with splice in react

Newbie question.
I have an array that i need to add to and I am using slice to do this. I am using gatsby/react. The problem I have is each time my page/component rerenders the object I am adding to my array gets added again
Here is my code
class IndexPage extends PureComponent {
render() {
const data = this.props.data;
const hostels = data.featuredHostel.edges;
const hopimage = data.hop.childImageSharp.fluid;
hostels.splice(8, 0, {
node: {
featuredImage: {
alt: 'Bedhopper Image',
fluid: hopimage
},
id: 'bedhopper',
slug: '/deals/bed-hopper',
title: 'For travel adicts who want to stay everywhere'
}
});
return (....
Been stuck on this for a while now. Any help appreciated
You should make any calculation on constructor or componentDidMount.
class IndexPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
hostels: props.data.featuredHostel.edges.concat(...)
}
}
componentDidMount() {
}
render() {
const { hostels } = this.state;
return (
...
)
Probably, your case can works too (I didn't see whole code). I guess you use array index as key for render
hostels.map((hostel, hostelIndex) => (<SomeComponent key={hostelIndex} />))
You can change key to hostel.id for example for more unique block.

Categories

Resources