I'm trying to render a state via props to a nested component. This is what I have in my App.js:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [
{name: 'Money Trees',
artist: 'Kendrick Lamar',
album: 'Album Title',
id: '1234'},
{name: 'Reptilia',
artist: 'The Stroks',
album: 'Is This It',
id: '234'}
]
};
}
render() {
return (
<div>
<h1>
Ja<span className="highlight">mmm</span>ing
</h1>
<div className="App">
<SearchBar />
<div className="App-playlist">
<SearchResults searchResults={this.state.searchResults} />
<Playlist />
</div>
</div>
</div>
);
}
}
export default App;
Inside Playlist.js I have this:
class Playlist extends React.Component {
render() {
return (
<div className="Playlist">
<input defaultValue={'New Playlist'} />
<Tracklist />
<a className="Playlist-save"></a>
</div>
);
}
}
export default Playlist;
Now, I'm able to pass in the tracks props through a SearchResults component and into a Tracklist component. However, I'm getting an error when trying to map the tracks prop:
TypeError: Cannot read property 'map' of undefined
When I remove the Playlist component from the App file, the error fixes itself. I'm not sure why I get the error when I toss in <Playlist /> inside the main App.js file. Can someone help me understand that?
This is what Tracklist.js looks like:
class Tracklist extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<div className="TrackList">
{this.props.tracks.map(track => <Track track={track} key={track.id} />)}
</div>
);
}
}
export default Tracklist;
Playlist renders Tracklist and it expects tracks props.
Try:
class Playlist extends React.Component {
render() {
return (
<div className="Playlist">
<input defaultValue={'New Playlist'} />
<Tracklist tracks={[]} />
<a className="Playlist-save"></a>
</div>
);
}
}
To see if the error disappears.
Related
I can't figure out why I am not able to dynamically render my Movies component. I have a Movie Component that models each individual Movie. I then have a Movies component which maps every Movie from my api get request in index.jsx. It says, this.props.movies.map is not a function inside my Movies components. I quadrupled check that I was sending props the right way and it should be in the form of an array but nothing is working.
index.jsx:
import React from 'react';
import ReactDOM from 'react-dom';
import Movies from './components/Movies.jsx'
import axios from 'axios';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
movieList: [],
};
}
componentDidMount() {
axios.get('https://api.themoviedb.org/3/movie/550?api_key=f3a857449b25e45a2c69af11e38fe7de')
.then(res => {
this.setState({ movieList: res.data })
})
.catch(err => {
console.log(err);
})
}
render () {
return (
<div className="app">
<header className="navbar"><h1>Bad Movies</h1></header>
<div className="main">
<Movies
movies={this.state.movieList}
/>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Movie.jsx:
import React from 'react';
class Movie extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<ul className="movie">
<li className="movie_item">
<img src={`https://image.tmdb.org/t/p/original/${this.props.movie.poster_path}`} />
<div className="movie_description">
<h2>{this.props.movie.title}</h2>
<section className="movie_details">
<div className="movie_year">
<span className="title">Year</span>
<span>{this.props.movie.release_date.split('-')[0]}</span>
</div>
<div className="movie_rating">
<span className="title">Rating</span>
<span>{this.props.movie.vote_average}</span>
</div>
</section>
</div>
</li>
</ul>
</div>
);
}
}
export default Movie;
Movies.jsx:
import React from 'react';
import Movie from './Movie.jsx'
class Movies extends React.Component {
constructor(props) {
super(props);
}
render() {
const mappedMovies = this.props.movies.map(movieObj => <Movie key={movieObj.id} movie={movieObj}/>); //This is where the error is pointing towards
return (
<div>
{mappedMovies}
</div>
);
}
}
export default Movies;
As Delice correctly said, there is a spelling mistake.
If you want this to be movie instead of movies, then change the below in index.js
<div className="main">
<Movies
movie={this.state.movieList}
/>
UPDATE:
Your API returns an object yet your state is set to an empty array as the initial value.
Then you populate your state movieList with the response from your API.
That means your movieList state actually becomes an object - therefor you get an error saying that
movies is not a function
You cannot map an object.
Remember to tell your component to only pass movieList when it is populated with data like so:
render () {
return (
<div className="app">
<header className="navbar"><h1>Bad Movies</h1></header>
<div className="main">
{this.state.movieList && <Movies
movies={this.state.movieList}
/> }
</div>
</div>
);
}
I am passing array of object as a prop from App.js -> searchResult -> TrackList.js. But when I apply .map function on the array of object it shows Cannot read property 'map' of undefined . I have tried different ways to solve this but none of them worked. In console I am getting the value of prop. And my TRackList.js component is rendering four times on a single run. Here is the code
App.js
this.state = {
searchResults: [
{
id: 1,
name: "ritik",
artist: "melilow"
},
{
id: 2,
name: "par",
artist: "ron"
},
{
id: 3,
name: "make",
artist: "zay z"
}
]
return ( <SearchResults searchResults={this.state.searchResults} /> )
In Searchresult .js
<TrackList tracked={this.props.searchResults} />
In TrackList.js
import React from "react";
import Track from "./Track";
export default class TrackList extends React.Component {
constructor(props) {
super();
}
render() {
console.log("here", this.props.tracked);
return (
<div>
<div className="TrackList">
{this.props.tracked.map(track => {
return (<Track track={track} key={track.id} />);
})}
</div>
</div>
);
}
}
Here is the full code -- https://codesandbox.io/s/jamming-ygs5n?file=/src/components/TrackList.js:0-431
You were loading the Component TrackList twice. One time with no property passed, that's why it was first set in console then looks like it's unset, but it's just a second log. I have updated your code. Take a look here https://codesandbox.io/s/jamming-ddc6l?file=/src/components/PlayList.js
You need to check this.props.tracked.map is exists before the loop.
Solution Sandbox link:https://codesandbox.io/s/jamming-spf7f?file=/src/components/TrackList.js
import React from "react";
import Track from "./Track";
import PropTypes from 'prop-types';
export default class TrackList extends React.Component {
constructor(props) {
super();
}
render() {
console.log("here", typeof this.props.tracked);
return (
<div>
<div className="TrackList">
{this.props.tracked && this.props.tracked.map(track => {
return <Track track={track} key={track.id} />;
})}
</div>
</div>
);
}
}
TrackList.propTypes = {
tracked: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
artist: PropTypes.string,
}))
};
You need to check this.props.tracked value before implementing the map function.
you can simply check using this.props.tracked && follow your code.
You should add searchResults={this.state.searchResults} in your app.js to Playlist, take it in Playlist with props, and then set it in TrackList from Playlist (tracked={props.searchResults}).
Also, Typescript helps me not to do such mistakes.
Also, add a key prop to your component that you return in the map function.
Here's the code:
export default class Collage extends React.Component {
constructor() {
super();
this.state = {
images: [
<this.Image src="" />,
],
};
}
Image = ({ src }) => (
<img className="collage__img" alt="" src={src} onTransitionEnd={evt => evt.target.remove()} />
);
render() {
return (
<div className="collage">
{this.state.images}
</div>
);
}
}
All I want is to generate a list of images in .collage block before everything is rendered. As you see, I tried to create images in the constructor. But this way I get an error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
But if I declare Image in the constuctor (without using this) everything works fine. Such a strange behavior.
Could you tell, is there another way to generate data before render?
Create your Image component class outside of College.js. Also remember to add in a "key" attribute on JSX which are stored in array to help react run faster.
Codesandbox
Image.js
import React from "react";
const Image = ({ src }) => (
<img
className="collage__img"
alt=""
src={src}
onTransitionEnd={evt => evt.target.remove()}
/>
);
export default Image;
College.js
import React from "react";
import Image from "./Image";
export default class Collage extends React.Component {
constructor() {
super();
this.state = {
images: [
<Image
key="a356f8ff-0fc4-4c00-afb4-8ce60fcc210e"
src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/Test-Logo.svg/1200px-Test-Logo.svg.png"
/>
]
};
}
render() {
return <div className="collage">{this.state.images};</div>;
}
}
App.js
import React from "react";
import "./styles.css";
import Collage from "./College";
export default function App() {
return (
<div className="App">
<Collage />
</div>
);
}
I don't know why you don't put <Image /> as separate component since it doesn't depend on anything in your class context as below:
const Image = ({ src }) => (
<img className="collage__img" alt="" src={src} onTransitionEnd={evt => evt.target.remove()} />
);
class Collage extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [
<Image src="" />
],
};
}
render() {
return (
<div className="collage">
{this.state.images}
</div>
);
}
}
I don't know why you want to put your components in the state but this is not optimized. I think the best way to do that is somethink like this:
export default class Collage extends React.Component {
constructor() {
super();
this.state = {
images: [
{src: ""}
],
};
}
Image = ({ src }) => (
<img className="collage__img" alt="" src={src} onTransitionEnd={evt => evt.target.remove()} />
);
render() {
return (
<div className="collage">
{this.state.images.map(this.Image)}
</div>
);
}
}
You can use react lifecycle method componentWillMount() which will generate a list of images in .collage block before everything is rendered.
new to react and trying to passdown, props to components with react. but gets undefined result when receiving {this.props.name} in Track.js. And TypeError: Cannot read property 'name' of undefined
when trying with {this.props.track.name} in Track.js render method.
It should render these tracks:
tracks: [{
name: 'Gold Slug',
artist: 'DJ khaled',
album: 'We da best',
id: '102 sample ID'
},
{
name: 'Slim shady',
artist: 'Eminem',
album: 'Marshal materials',
id: '103 sample ID'
}
Or this track:
searchResults: [{
name: 'You Mine',
artist: 'DJ Khaled',
album: 'I Changed a Lot',
id: '101 sample ID'
}],
Background info: Building an app that renders song tracks with name, artist and album. Also a search with search results and a playlist. I'm working with 6 components: App, PlayList, SearchBar, SearchResults, Track and TrackList.
App is a container. PlayList lets users adds Tracks to it. SearchResults displays results from SearchBar.
In App.js, I've set the state of searchResults in the constructor method and passed it down to component.
App.js:
import React from 'react';
import './App.css';
import SearchBar from '../SearchBar/SearchBar'
import SearchResults from "../SearchResults/SearchResults";
import Playlist from "../Playlist/Playlist";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [{
name: 'You Mine',
artist: 'DJ Khaled',
album: 'I Changed a Lot',
id: '101 sample ID'
}],
tracks: [{
name: 'Gold Slug',
artist: 'DJ khaled',
album: 'We da best',
id: '102 sample ID'
},
{
name: 'Slim shady',
artist: 'Eminem',
album: 'Marshal materials',
id: '103 sample ID'
}
]
};
}
render() {
return (
<div>
<h1>Ja<span className="highlight">mmm</span>ing</h1>
<div className="App">
<SearchBar />
<div className="App-playlist">
<SearchResults searchResults={this.state.searchResults}/>
<Playlist />
</div>
</div>
</div>
);
}
}
export default App;
SearchResults receives and .map iterates through the array and sets it to tracks within the TrackList component within render method.
SearchResults.js:
import React from 'react';
import './SearchResults.css'
import TrackList from "../TrackList/TrackList";
import Track from "../Track/Track";
class SearchResults extends React.Component {
render() {
return (
//Adds a map method that renders a set of Track components on the tracks attribute.
<div className="SearchResults">
<h2>Results</h2>
<TrackList tracks={this.props.searchResults.map(track => {
return <Track key={track.id} /> }
)} />
</div>
)
}
}
export default SearchResults;
TrackList.js renders a set of track components:
import React from 'react';
import './TrackList.css'
import Track from '../Track/Track'
class TrackList extends React.Component {
render() {
return (
<div className="TrackList">
<Track />
<Track />
<Track />
<Track />
</div>
)
}
}
export default TrackList;
Track.js renders Tracks:
import React from 'react';
import './Track.css'
class Track extends React.Component {
renderAction() {
if (this.props.isRemoval == true) {
return <h1>-</h1>;
} else {
return <h1>+</h1>;
}
}
//TODO: Fix rendering method on this.props.track.name
render() {
return (
<div className="Track">
<div className="Track-information">
<h3>{this.props.name}</h3>
<p>{`${this.props.artist} | ${this.props.album}`}</p>
</div>
<a className="Track-action" isRemoval={true}></a>
</div>
)
}
}
export default Track;
The issue here is that you are not actually passing data down to the Track component.
class SearchResults extends React.Component {
render() {
return (
//Adds a map method that renders a set of Track components on the tracks attribute.
<div className="SearchResults">
<h2>Results</h2>
<TrackList tracks={this.props.searchResults.map(track => {
return <Track key={track.id} /> }
)} />
</div>
)
}
}
Seems like SearchResults is trying to map thru the data is got via props, and render a Track for each one. Now if you look at this line right here, you will notice that no data is actually passed.
return <Track key={track.id} /> }
I think to fix your issue, all you would need to do is this.
class SearchResults extends React.Component {
render() {
return (
//Adds a map method that renders a set of Track components on the tracks attribute.
<div className="SearchResults">
<h2>Results</h2>
<TrackList tracks={this.props.searchResults.map(track => {
return <Track key={track.id} track={track} /> }
)} />
</div>
)
}
}
Now in the Track component you can access this.props.track.whatever.
I have below codes
chat.js
import React from 'react';
import '../styles/Chat.css';
import Web from '../services/Web';
class Chat extends React.Component {
constructor(props) {
super(props);
this.state = {
msg:''
};
this.sendMessage = this.sendMessage.bind(this);
}
sendMessage () {
this.props.updatecommentText(this.refs.newText.value, this.props.index);
this.setState({ msg: '' });
}
render() {
return (
<div className="Chat-container">
<div className="Chat-row">
<div className="Chat-column">
<div className="Chat-card">
<div className="Chat-body">
<div className="Chat-title">React Based Chatbot</div>
<div className="Chat-messages">
{ this.props.children }
</div>
</div>
<div className="Chat-footer">
<textarea className="Chat-input" ref="newText"></textarea>
<button className="Chat-submit" onClick={this.sendMessage} defaultValue={ this.props.children }>Send</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Chat;
Web.js
import React, { Component } from 'react';
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import Chat from '../components/Chat';
class Web extends React.Component {
constructor(props){
super(props);
this.state = {
messages:["Hi, How can I help you ?"
]
};
this.sendtobot = this.sendtobot.bind(this);
}
sendtobot(newText, i){
var arr = this.state.messages
arr.push(newText)
this.setState({messages: arr})
}
eachMessage(message, i){
return (<Chat key={i} index={i} updatecommentText={ this.sendtobot.bind(this) }>{ message }</Chat>);
}
render(){
return(
<div>
{this.state.messages.map(this.eachMessage.bind(this))}
</div>
)
}
}
export default Web;
I wanted to take the input from the Chat.js and send it to Web.js and push that value to array messages and then again render that array in the this.props.children in Chat.js
But, while running the code, I am getting an error this.props.updatecommentText is not a function.
Can someone please help me with this.
You have bind this.sendtobot twice. It should be only in the constructor.
like this
eachMessage(message, i){
return (
<Chat key={i} index={i} updatecommentText={this.sendtobot}>
{ message }
</Chat>
);
}
Your code seems to work.
Here is a sandbox with your code.
I'm not sure it works as you would expect, but it works without errors.
By changing this 3 functions in Web component, it starting to look like a chat with only one textarea
sendtobot(newText, i) {
this.setState({ messages: [...this.state.messages, newText] })
}
eachMessage(message, i) {
return (<p>{message}</p>);
}
render() {
return (
<div>
{this.state.messages.map(this.eachMessage.bind(this))}
<Chat updatecommentText={this.sendtobot}/>
</div>
)
}
You can pass child's component state to parent component using redux also as global state.