I am an extreme newcomer to development, and as such I might be missing something obvious. I am busy creating a simple React app to display an array of objects. I have hard coded the general idea of an array in the parent component and passed it through to the child components. However, when I try logging the prop to console, it first appears as the object should, then logs again as undefined. This is making it really difficult to call an Object.keys() function on it and try map it to an array.
Below is an example of the code:
export class Parent extends React.Component() {
constructor(props) {
super(props);
this.state = {
arr: []
}
}
render() {
return (
<div>
<child array={this.state.arr} />
</div>
)
}
}
Then the child follows:
export class Child extends React.Component() {
renderArray() {
let arr = this.props.array;
console.log(arr);
return Object.keys(arr).map(arrayItem => {
let title = arr[arrayItem];
return <li key={title.id}> {title.name}</li>
}
}
render() {
return (
<div>
{this.renderArray()}
</div>
)
}
}
If I call console.log on arr object in the child component, it returns the props correctly. But then right after that it logs a second instance of 'undefined'.
EDIT: Lots of comments about empty array. Please see code below.
Here is the parent component.
import React, { Component } from 'react';
import './App.css';
import { SearchBar } from '../SearchBar/SearchBar.js';
import { SearchResults } from '../SearchResults/SearchResults.js';
import { Playlist } from '../Playlist/Playlist.js';
class App extends React.Component {
constructor(props){
super(props);
this.state = {
searchResults: [
{
"id": 2011,
"name": 'What Makes A Man',
"artist": 'Man As Machine',
"album": 'Nothing but a thing'
},
{
"id": 2056,
"name": 'Pushpin',
"artist": 'Man As Machine',
"album": 'Patterns'
},
{
"id": 2099,
"name": 'Zombie',
"artist": 'Man As Machine',
"album": 'Patterns'
}
],
playlistName: ''
}
}
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;
Then I have the searchResults component (child):
import React from 'react';
import './SearchResults.css';
import { Tracklist } from '../Tracklist/Tracklist.js';
export class SearchResults extends React.Component {
render () {
return (
<div className="SearchResults">
<h2>Results</h2>
<Tracklist tracks={this.props.searchResults}/>
</div>
)
}
}
and finally the tracklist component:
import React from 'react';
import './Tracklist.css';
import { Track } from '../Track/Track.js';
export class Tracklist extends React.Component {
constructor(props) {
super(props);
}
renderTrackList() {
let tracks = this.props.tracks;
console.log(tracks);
return Object.keys(tracks).map(track => {
let trackItem = tracks[track];
return <Track key={trackItem.id} track={trackItem}
})
}
render () {
return (
<div className="TrackList">
{this.renderTrackList()}
</div>
)
}
}
Here is the searchBar component:
import React from 'react';
import './SearchBar.css';
export class SearchBar extends React.Component {
render() {
return (
<div className="SearchBar">
<input placeholder="Enter A Song, Album, or Artist" />
<a>SEARCH</a>
</div>
);
}
}
and here is the Track component:
import React from 'react';
import './Track.css';
export class Track extends React.Component {
renderAction (isRemoval) {
if (this.props.isRemoval){
return <a className="Track-action" onClick={this.removeTrack}>-</a>
} else {
return <a className="Track-action" onClick={this.addTrack}>+</a>
}
}
render () {
return (
<div className="Track">
<div className="Track-information">
<h3>{this.props.track.name}</h3>
<p>{this.props.track.artist} | {this.props.track.album}</p>
</div>
<a className="Track-action">{this.renderAction}</a>
</div>
)
}
}
you're working with an array of something, and Object.keys() is for iterate on object property keys.
You can iterate in your array directly with map().
return arr.map(arrayItem => {
let title = arr[arrayItem];
return <li key={title.id}> {title.title}</li>
})
Or you can do it better because here you are missing the <ul> element
export class Child extends React.Component() {
renderArray() {
if(this.props.array && this.props.array.length >0){
return (
<ul>
{this.props.array.map(item => <li key={item.id}>{item.title}</li>)
</ul>
)
}
return 'No item in array.'
}
render() {
return (
<div>
{this.renderArray()}
</div>
)
}
}
You've to put some data in your array
this.state={
arr:[
{id:1,title:"item 1"},
{id:2,title:"item 2"}
]
}
Child component be Child but not child when you call it. Please try below solution
import Child from “./Child”;
export class Parent extends React.Component() {
constructor(props) {
super(props);
this.state = {
arr: [
{"id":1, name:"test"}, {"id": 2, "name": "test2"}
]
}
}
render() {
return (
<div>
<Child array={this.state.arr} />
</div>
)
}
}
Child component
export class Child extends React.Component() {
renderArray = (array) => {
return array.map(item=> (
<li key={item.id}> {item.name}</li>
)
}
render() {
const {array} = this.props;
return (
<div>
<ul>
{this.renderArray(array)}
</ul>
</div>
)
}
}
Please excuse me for wrong double quotes because I am answering on my phone
Related
I am following a tutorial. I don't get why totalCounters is null. I searched online but I do not understand it.
The error message I get is :
TypeError: Cannot read property 'counters' of null.
I followed the tutorial from Mosh.
This is my App.js file.
import React, { Component } from "react";
import NavBar from "./components/navbar";
import Counters from "./components/counters";
import "./App.css";
class App extends Component {
render() {
return (
<React.Fragment>
<NavBar totalCounters={this.state.counters.length} />
<main className="container">
<Counters
counters={this.counters}
onReset={this.handleReset}
onIncrement={this.handleIncrement}
onDelete={this.handleDelete}
/>
</main>
</React.Fragment>
);
}
}
export default App;
This is my navbar.jsx
import React, { Component } from "react";
class NavBar extends Component {
render() {
return (
<nav className="navbar navbar-light bg-light">
<a className="navbar-brand" href="#">
Navbar <span className="badge badge-pill badge-secondary">{this.props.totalCounters}</span>
</a>
</nav>
);
}
}
export default NavBar;
This is my counters.jsx
import React, { Component } from "react";
import Counter from "./counter";
class counters extends Component {
state = {
counters: [
{ id: 1, value: 5 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
handleIncrement = counter => {
const countersCopy = [...this.state.counters];
const index = countersCopy.indexOf(counter);
countersCopy[index] = { ...counter };
countersCopy[index].value++;
this.setState({ counters: countersCopy });
};
handleReset = () => {
const resetCounters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({ counters: resetCounters });
};
handleDelete = counterId => {
const newCounters = this.state.counters.filter(c => c.id !== counterId);
this.setState({ counters: newCounters });
};
render() {
return (
<div>
<button
onClick={this.handleReset}
className="btn btn-primary btn-sm m2"
>
Reset
</button>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onDelete={this.props.onDelete}
onIncrement={this.handleIncrement}
counter={counter}
/>
))}
</div>
);
}
}
export default counters;
In React, this.state is local to each component.
So, setting this.state.counters in counters does not allow App component to use the state.
This is why counters is null in App component.
Because you don't have a state field into your App class components.
Everywhere you want to use state, you have to create a state object.
Class field
class App extends Component {
state = { counters: [] }
}
Inside contructor
class App extends Component {
contructor(props) {
super(props)
this.state = { counters: [] }
}
}
You are not initializing the state. Your state is undefined. Fix it like this
class App extends Component {
this.state = { counters : [] }
}
This is my code
import React, { Component } from "react";
import { render } from "react-dom";
class CView extends Component {
someFunc() {
alert(1);
}
render() {
return <div>Hello, there</div>;
}
}
class App extends Component {
getControl() {
this.cv = <CView />;
return this.cv;
}
render() {
return (
<div>
<h2 onClick={() => this.cv.someFunc()}>Click Me</h2>
{this.getControl()}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Also available on https://codesandbox.io/s/k2174z4jno
When I click on the h2 tag, I get an error saying someFunc is not defined. How can I expose a function of a component so that other components can access it?
Thanks
I think that this.cv = <CView />; will not directly return instance of CView component.
onClick={() => {
console.log(this.cv instanceof CView); // false
this.cv.someFunc();
}}
But if you try to use refs you will access it.
class App extends Component {
constructor(props) {
super(props)
this.cv = React.createRef();
}
onClick() {
this.cv.current.someFunc();
}
render() {
return (
<div>
<h2 onClick={() => this.onClick()}>Click Me</h2>
<CView ref={this.cv} />
</div>
);
}
}
It is more "React way" though. https://codesandbox.io/s/vy61q9o8xy
Task is to fetch data from api when toggle between tags
When click on the link it calls the api service but state of feeds is not updated but it throws below warning
jQuery.Deferred exception: Cannot read property 'setState' of undefined TypeError: Cannot read property 'setState' of undefined
My github repo
https://github.com/dolphine4u/demo-app
APP component
import React from 'react';
import {FetchData} from "../service/flickerApi.service";
import Header from "./header/header.component";
import Navigation from "./navigation/navigation.component";
import ProductList from "./products/products.component";
import Footer from "./footer/footer.component";
class App extends React.Component {
constructor() {
super()
this.state = {
feeds: [],
favorites:[]
};
this.addToFavorites = this.addToFavorites.bind(this);
}
handleChange( value ) {
this.setState( { feeds: value })
}
addToFavorites(id) {
const {feeds ,favorites} = this.state;
const findId = feeds.filter(item => {
return item.id === id;
})
favorites.push(findId)
console.log(favorites)
// localStorage.setItem('favorite', JSON.stringify(this.state.favorites));
this.setState({
feeds: favorites
});
}
/* componentWillMount(){
let LoadFeeds = localStorage.getItem('FlickerFeeds');
LoadFeeds && this.setState({
feeds: JSON.parse(LoadFeeds)
})
}*/
componentDidMount() {
FetchData.call(this);
}
/* componentWillUpdate(nextprops, nextState){
localStorage.setItem('FlickerFeeds', JSON.stringify(nextState.feeds))
}
*/
render() {
const {feeds} = this.state;
const productList = feeds.map((item,index) => {
return <ProductList
key={index}
title={item.title}
image={item.src}
id={item.id}
author={item.author}
date={item.created}
update={this.addToFavorites}
/>
})
return ([
<Header key="header"/>,
<Navigation key="navigation" />,
<section key="productList">
<div className="container">
<div className="row row-eq-height">
{productList}
</div>
</div>
</section>,
<Footer key="footer"/>
]);
}
}
export default App;
Navigation component
import React from 'react';
import Link from "./link.component";
import './navigation.css';
class Navigation extends React.Component {
constructor(props) {
super(props)
this.state = {
tags: [
{tag:"kittens"},
{tag:"dogs"},
{tag:"lion"},
{tag:"tiger"},
{tag:"leapord"}]
};
}
render() {
const {tags} = this.state;
const tagList = tags.map(item => {
return <Link
key={item.tag}
tag={item.tag}
/>
})
return (
<nav className="nav">
<div className="container">
<ul className="nav-bar">
{tagList}
</ul>
</div>
</nav>
);
}
}
export default Navigation;
Link Component
import React from 'react';
import {FetchData} from "../../service/flickerApi.service";
class Link extends React.Component {
constructor(props) {
super(props)
this.onClick = this.onClick.bind(this);
}
onClick(e) {
FetchData(this.props.tag);
}
render() {
return (
<li><a href="#" onClick={this.onClick}>{this.props.tag}</a></li>
);
}
}
export default Link;
product component
import React from 'react';
import './product.css';
class ProductList extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
this.props.update(this.props.id);
}
render() {
return (
<div className="product-column">
<div className="product-item">
<div className="product-content">
<div className="product-author">
<strong>Author: </strong>{this.props.author}
</div>
{/*<div className="product-image" style={{backgroundImage: "url(" + this.props.image + ")"}}/>*/}
</div>
<div className="product-content">
<div className="product-date">
Created Date: {this.props.date}
</div>
<h3 className="product-title">{this.props.title}</h3>
<button className="product-btn" onClick={this.onClick}>
Add to Favourites
</button>
</div>
</div>
{/*<div className="product-description" dangerouslySetInnerHTML={{__html: this.props.description}}>
</div>*/}
</div>
);
}
}
export default ProductList;
Api service
import $ from "jquery";
import {getLastPartOfUrl, formatDate, removeUrl, getString} from "../helpers/helper";
export function FetchData(tag) {
const URL = "https://api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=?"
const SUFFIX_SMALL_240 = "_m";
const SUFFIX_SMALL_320 = "_n";
$.getJSON({
url : URL,
data: {
tags: tag
}
})
.then(response => {
let list= response.items.map(item => ({
title: removeUrl(item.title),
id: getLastPartOfUrl(item.link),
description: item.description,
link: item.link,
src: item.media.m.replace(SUFFIX_SMALL_240, SUFFIX_SMALL_320),
author: getString(item.author),
created: formatDate(item.published),
tags: item.tags,
fav: false
}));
this.setState({
feeds: list
})
}).catch(function(error){
console.log(error);
});
}
You're trying to call this.addToFavorites from a click handler that is not even bound to this. I think two changes are needed for this to work:
In App component, change the addFavorites function to an arrow function so it gets the context this:
addToFavorites = id => {
...
Same in ProductList component for the click handler:
onClick = () => {
this.props.update(this.props.id);
}
single.js :
import React, { Component } from 'react';
import Details from '../components/details'
import { ProgressBar } from 'react-materialize';
import { Route, Link } from 'react-router-dom';
const Test = () => (
<div> RENDER PAGE 1</div>
)
class SinglePage extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
componentDidMount() {
fetch('http://localhost:1337/1')
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
render() {
const { data } = this.state;
return (
<div>
<h2> SinglePage </h2>
{!data ? (
<ProgressBar />
) : (
<div>
<Details data={data} />
</div>
)}
</div>
);
}
}
export default SinglePage;
details.js :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Details extends Component {
static propTypes = {
item: PropTypes.shape({
date: PropTypes.string.isRequired,
}).isRequired,
}
render() {
const { item } = this.props;
return (
<div>
<p> {item.date} </p>
</div>
)
}
}
export default Details;
In console, I am getting an error : Warning: Failed prop type: The prop item is marked as required in Details, but its value is undefined.
From this I though my json was not catched but I have an other component which fetch on http://localhost:1337/ , get datas and display them correctly, and going to http://localhost:1337/1 send me a json response so I'm quite confused here.
Additional screenshot :
SinglePage is passing date props with name data as oppose to item that is defined in Details
<Details item={date} />
Also adding init value for date
constructor(props) {
super(props);
this.state = {
date: { date: null },
}
}
I am trying to implement Presentational and Container Components pattern when creating React components. So I created a presentational component with only UI elements and container component with handling data capabilities.
component.jsx
import React from "react";
const MyComponent = ({props}) => (
<div>
{props.games.map((game, index) => (
<div key={index}>
{game.index + 1} - {game.contestName}
</div>
))};
</div>
);
export default MyComponent;
container.jsx
import React, { Component } from "react";
import MyComponent from "./component";
class MyContainer extends Component {
constructor(props) {
super(props);
this.state = {
games: [
{
id: 1,
categoryName: "Business/Company",
contestName: "Name1"
},
{
id: 2,
categoryName: "Magazine/Newsletter",
contestName: "Name2"
},
{
id: 3,
categoryName: "Software Component",
contestName: "Name3"
},
{
id: 4,
categoryName: "Website",
contestName: "Name4"
}
]
};
}
render() {
return (
<div>
<MyComponent games={this.state.games} />
</div>
);
}
}
export default MyContainer;
However, I can not render data and I get
Uncaught TypeError:
Cannot read property 'games' of undefined.
Would really appreciate your help, as two days of internet digging has not yielded positive results.
const MyComponent = ({props}) => (
When you do this, you actually do
{ props: props-received-from-parent }
You are enclosing your props in another object, remove those braces and change that line to
const MyComponent = (props) => (
and you are good to go.
You should destructure your games instead of props:
import React from "react";
const MyComponent = ({games}) => (
<div>
{games.map((game, index) => (
<div key={index}>
{game.index + 1} - {game.contestName}
</div>
))};
</div>
);
export default MyComponent;
You can define your MyComponent class like this
class MyComponent extends Component{
render(){
this.xyz = this.props.games.map((item,index) => {
return(<div key={index}>{item.id}</div>)
})
return(
<div>
{this.xyz}
</div>
)
}
}
export default MyComponent;
This will also work!