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.
Related
I'm sure this was asked before but I can't find it. I'm trying to pass a nested object as props into a React.Component class but I keep getting this error:
react-dom.development.js:13231 Uncaught Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater}). If you meant to render a collection of children, use an array instead.
I have an App.tsx file created like this:
import React from "react";
export type Child = {
id: number
}
export type Parent = {
id: number,
child: Child
}
export class Card extends React.Component<Parent> {
render() {
return <div>
Parent ID: {this.props.id}
Child ID: {this.props.child.id}
</div>;
}
}
function App() {
const data: any = {
"id": 1,
"child": {
"id": 2
}
}
const card: Card = new Card(data);
return (
<div className="app">
{card}
</div>
);
}
export default App;
Is this sort of thing not possible? It seems like it should be but maybe I am missing something. Is there a way to make something like this work or a correct pattern that I am not using? Thanks!
Theere is no "one" correct way to pass an object down to a child component, there are multiple approaches, one of which is the following (using your example):
import React from "react";
export type Child = {
id: number
}
export type Parent = {
id: number,
child: Child
}
export class Card extends React.Component<Parent> {
render() {
return <div>
Parent ID: {this.props.id}
Child ID: {this.props.child.id}
</div>;
}
}
function App() {
const data: any = {
"id": 1,
"child": {
"id": 2
}
}
return (
<div className="app">
<Card {...data} />
</div>
);
}
export default App;
I'm pretty sure for this to work you'd have to change
<div className="app">
{card}
</div>
TO:
<div className="app">
<card />
</div>
BUT, like Jack said in the comments, you shouldn't be instantiating React components like this anyway. Instead of doing:
const card: Card = new Card(data);
You should just do:
<div className="app">
<Card {...data} />
</div>
I have a React component that maps data from a .js file, containing objects in array. How can I link my title and images from within this function to access respective pages.
The title of an array of objects is 'Practices' which should open a new practices page when clicked. Within this object, there are a number of items, each of which contain an image of a practice, which when clicked must go to that specific practice page.
Here is sample code from my Practice_Data File..
const PRACTICES_DATA = [
{
id: 1,
title: 'Practices',
routeName: 'practices',
items: [
{
id: 1,
name: 'Defending Centrally | Opposed Skill (12-P5)',
imageUrl: 'https://image.mux.com/D8PU0036BeX3veIIzAKJlqiECDBoEIJak/animated.gif?width=490&height=278&fps=15',
Theme: 12,
},
]
},
export default PRACTICES_DATA;
Here is the code for my practice component:
import React from 'react';
import PRACTICES_DATA from './practices.data';
import CollectionPreview from '../../components/collection-preview/collection-preview';
class PracticesPage extends React.Component {
constructor(props) {
super(props);
this.state = {
collections: PRACTICES_DATA
};
}
render() {
const {collections} = this.state;
return (
<div className='practices-page'>
{collections.map(({ id, ...otherCollectionProps }) => (
<CollectionPreview key={id} {...otherCollectionProps} />
))}
</div>
);
}
}
export default PracticesPage;
There might well be an obvious answer to this, but I have been stuck on this for a while. Any help would be greatly appreciated.
EDIT:
Yes using react router here. The Collection Preview Code is shown below:
import React from 'react';
import CollectionItem from '../collection-item/collection-item.component';
import './collection-preview.styles.scss';
const CollectionPreview =({ title, items }) => (
<div className='collection-preview'>
<h1 className='title'>{title.toUpperCase()}</h1>
<div className='preview'>
{items.filter((item, idx) => idx < 4)
.map(({ id, ...otherItemProps }) => (
<CollectionItem key={id} {...otherItemProps} />
))}
</div>
</div>
);
export default CollectionPreview;
I have this React component
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
this.setState({
resources: this.props.location.resources,
});
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
It gets the resources from the Link component, and that works fine. If I check out the state of the Component from the dev tools, the state looks right. And I thought with my logic this should work. So firstly, the state is empty, the component gets rendered, since the state is empty it doesn't render any components. Then, setState gets called, it gets all the resources and saves them into the state, and then the component would re-render, and it should work, but it doesn't. I'm getting a TypeError: Cannot read property 'map' of undefined error. What is the correct way to do this and how do I fix this?
Try this code:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: this.props && this.props.location && this.props.location.resources?this.props.location.resources:[],
};
}
componentDidMount() {
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or use directly props
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{
this.props && this.props.location &&
this.props.location.resources
?this.props.location.resources.map(res => (
<div>test</div>
))
:null
}
</div>
);
}
}
Or use componentWillReceiveProps or getDerivedStateFromProps life cycle methods.
Check this.props.location.resources is array.
See more: https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607
For first check is this.props.location.resources array, or if data type changes you can add checking, you can use lodash isArray or with js like this:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
Array.isArray(this.props.location.resources) {
this.setState({
resources: this.props.location.resources,
});
}
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or you can just use hooks like this:
import React, { useState, useEffect } from "react";
export default function ResourceForField({location}) {
const [ resources, setResources ] = useState([]);
useEffect(() => {
if (location && Array.isArray(location.resources)) {
setResources(location.resources)
}
}, [location]);
return (
<div>
{resources.map(res => (
<div>test</div>
))}
</div>
);
}
If the internal state of ResourceForField doesn't change and always equals to its prop, you shouldn't save the prop in the state. You can instead create a pure functional component.
Also note that there's nothing preventing you from initializing the state from the props in constructor method. i.e. you're not required to wait for the component to mount in order to access the props.
So, I'd write the following component for ResourceForField:
function ResourceForField({resources = []}) {
return (
<div>
{
resources.map(res => (<div>test</div>))
}
</div>
);
}
I'm building a KanBan app with ReactJS, and I'm trying to pass state from a parent component to the furthest component in the parent-child tree. I have a Column component within my main App component, and within this Column component there is another component called 'Card'. I want to pass the data the gets added/updated in the state of the App component & successfully display it in the Card component. As its obvious by now, the Card component is the child of the Column component.
I tried doing so with this.props but this only works one level down - with the column component. I thought about declaring a variable and equating to this.props.details.cards, and then setting it as the new state of the column component so that I could pass it again as props to the card component, but I assume this is not best practice.
This is my App Component:
class App extends Component {
constructor(props) {
super(props)
this.state = {
columns: [
{
name: 'Todos',
cards: []
},
{
name: 'Onprogress',
cards: []
},
{
name: 'Done',
cards: []
},
]
};
};
addCard = card => {
console.log("Adding a Card");
const cards = { ...this.state.columns.cards };
const keyDate = `card${Date.now()}`
cards[keyDate] = card;
this.setState({
columns: [
{
name: 'Todos',
cards: cards
}
]
});
};
render() {
return (
<div className="App">
{Object.keys(this.state.columns).map(key => (
<Column key={key} details={this.state.columns[key]} />
))}
<AddCardForm addCard={this.addCard} />
</div>
);
}
}
export default App;
This is my Column Component:
import React, {Component} from "react";
import Card from "./Card"
class Column extends Component {
render() {
return (
<div className="column">
<h1 className="Title">{this.props.details.name}</h1>
<Card />
</div>
);
}
}
export default Column;
And this is my Card Component:
import React, {Component} from "react";
class Card extends Component {
render() {
return (
<div className="card">
<span className="title">I'm a Card!</span>
</div>
);
}
}
export default Card;
React will only get the properties that are injected into the child component.
Considering we have three component A , B and C.
If we want to pass a prop from A to C while B is in the middle, we do this:
The A component returns the B component and passes someProperty as follows:
return <B someProperty={someValue} />
Now, in the B component we can access the property by calling it this.props.someProperty however, it won't be available at the C component, if we want to do so, we do the following.
return <C someProperty={this.props.someProperty} />
What we did in component B is pass the someProperty that came from component A which we can access in B as this.props.someProperty, we pass it again the same way to the component C..
Read more about this: Passing props between react component
Full example as follows:
Component A:
render() {
return (
<B someProperty={'someString'} />
);
}
Component B:
render() {
return (
<C someProperty={this.props.someProperty} />
);
}
Compoent C: to access the property someProperty for example in the render method
render() {
return (
<p>{this.props.someProperty}</p>
);
}
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.