How to render a React component inside of itself - javascript

I'm learning React and I'm trying to render the <Comment/> component inside of it self, however I get the following error:
TypeError: Cannot read property 'map' of undefined
Comment._this.getResponses
src/Comment.js:28
25 | );
26 | }
27 | getResponses = () => {
> 28 | return this.props.responses.map(p => {
| ^ 29 | return (
30 | <Comment
31 | author={p.author}
and the code:
import React, { Component } from "react";
class Comment extends Component {
render() {
return (
<div className="comment">
<a className="avatar">
<img src={this.props.avatar} />
</a>
<div className="content">
<a className="author">{this.props.author}</a>
<div className="metadata">
<span className="date">{this.props.timeStamp}</span>
</div>
<div className="text">
<p>{this.props.text}</p>
</div>
<div className="actions">
<a className="reply">Reply</a>
</div>
</div>
<div className="comments">{this.getResponses()}</div>
</div>
);
}
getResponses = () => {
return this.props.responses.map(p => {
return (
<Comment
author={p.author}
avatar={p.avatar}
timeStamp={p.timeStamp}
text={p.text}
/>
);
});
};
}
export default Comment;
Please note that this.props.responses is not undefined, and the problem only occurs while I'm trying to use the Comment component. If I replace the Comment component here:
return this.props.responses.map(p => {
return <Comment
author={p.author}
avatar={p.avatar}
timeStamp={p.timeStamp}
text={p.text}
/>
});
with something like this:
return this.props.responses.map(p => {
return (
<div>
<h1>author={p.author}</h1>
<h1>avatar={p.avatar}</h1>
<h1>timeStamp={p.timeStamp}</h1>
<h1>text={p.text}</h1>
</div>
);
});
the code works.

This is because the rendering of <Comment /> relies on the responses prop being defined.
Currently, when you render Comment components in getResponses(), there is no responses prop assigned to those comments:
<Comment
author={p.author}
avatar={p.avatar}
timeStamp={p.timeStamp}
text={p.text}
/>
This in turn means an error will be thrown when these <Comment /> components are rendered, and they attempt to render "children" of their own (during the call to getResponses()) via the undefined responses prop.
To resolve this, you can check to see that the this.props.responses array is defined before proceeding to map and render <Comment/> components in the getResponses() method, like so:
getResponses = () => {
// Check that responses prop is an array before
// attempting to render child Comment components
if(!Array.isArray(this.props.responses)) {
return null;
}
return this.props.responses.map(p => {
return (
<Comment
author={p.author}
avatar={p.avatar}
timeStamp={p.timeStamp}
text={p.text}
/>
);
});
};

Related

React: Rendering a method defined inside arrow function?

Hello friends! I hope you are well.
I've got an arrow function called WorldInfo and its parent component is passing down an object in props that for the sake of this example, I'm just calling object. Now In WorldInfo I also want to parse and list the items in object, so I've created the method serverInfoTabList to take object and shove it through .map. My problem is when compiled, my browser does not recognize serverInfoTabList either when it's defined nor called in WorldInfo's own return function.
Here is the error and the code itself.
Line 7:5: 'serverInfoTabList' is not defined no-undef
Line 34:22: 'serverInfoTabList' is not defined no-undef
const WorldInfo = (props) => {
serverInfoTabList = (object) => {
if (object != undefined){
return object.item.map((item) => {
const time = Math.trunc(item.time/60)
return (
<li key={item._id}>{item.name}
<br/>
Minutes Online: {time}
</li>
);
});
}
}
return (
props.object!= undefined ?
<div className={props.className}>
<h1>{props.world.map}</h1>
{/* <img src={props.object.image}/> */}
<div>
<ul>
{serverInfoTabList(props.object)}
</ul>
</div>
</div>
:
null
);
}
Thanks for your time friendos!
You forgot the const declaration
const serverInfoTabList = (object) => {
/* ... */
}
The other problem is that you're accessing properties which doesn't exist props.world for instance. Also you're mapping through an undefined property props.object.item. I've corrected your sandbox
const WorldInfo = props => {
const serverInfoTabList = object => {
return Object.keys(object).map(key => {
const item = object[key];
const time = Math.trunc(item.time / 60);
return (
<li key={item._id}>
{item.name}
<br />
Minutes Online: {time}
</li>
);
});
};
return props.object ? (
<div className={props.className}>
<h1>{props.world.map}</h1>
{/* <img src={props.object.image}/> */}
<div>
<ul>{serverInfoTabList(props.object)}</ul>
</div>
</div>
) : null;
};
class Todo extends Component {
render() {
const object = { item1: { _id: 1, time: 1 }, Item2: { _id: 2, time: 2 } };
return (
<div>
<WorldInfo object={object} world={{ map: "foo" }} />
</div>
);
}
}

TodoItems do not appear in UI using React and Firebase

I'm following Traversey Media's React crash course, and I wanted to extend what he built to save TodoItems to a Firebase realtime database. The TodoItems save just fine, but do not appear in the UI.
I've tried having the app start with a hard-coded TodoItem in case it was an issue of having an empty array, but this does not help anything.
relevant methods:
(full code available at https://github.com/etothepi16/react-todo)
// Delete todo item
delTodo = (id) => {
fire.database().ref(`todos/${id}`).remove()
.then(function(){
this.setState({
// Loop through todos array and filter out item with provided ID
// ... is the spread operator, used here to copy the todos array
todos: [...this.state.todos.filter((todo) => todo.id !== id)]
})
.catch(function(error){
console.log("Remove failed: " + error.message);
})
});
};
// Add todo
addTodo = (title) => {
let id = uuid.v4();
let database = fire.database();
let todosRef = database.ref(`todos/${id}`);
let newTodo = {
id: this.id,
title: title,
completed: false
}
todosRef.set(newTodo).then(
this.setState({todos: [...this.state.todos, newTodo]})
);
};
render() {
return (
<Router>
<div className='App'>
<div className='container'>
<Header />
<Route
exact
path='/'
render={(props) => (
<React.Fragment>
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.todos}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</React.Fragment>
)}
/>
<Route path='/about' component={About} />
</div>
</div>
</Router>
);
}
};
class Todos extends Component {
render(){
return this.props.todos.map((todo)=>(
<TodoItem todo={todo} markComplete={this.props.markComplete} delTodo={this.props.delTodo}/>
));
}
}
TodoItem.js
render() {
const { id, title } = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox" onChange={this.props.markComplete.bind(this, id)} />{' '}
{ title }
<button className="delete" style={btnStyle} onClick={this.props.delTodo.bind(this,id)}>x</button>
</p>
</div>
)
}
}
No TodoItems show up in the UI as stated before.
Error message:
Warning: Failed prop type: The prop todos is marked as required in TodoItem, but its value is undefined.
in TodoItem (at Todos.js:8)
in Todos (at App.js:87)
in Route (at App.js:81)
in div (at App.js:79)
in div (at App.js:78)
in Router (created by BrowserRouter)
in BrowserRouter (at App.js:77)
in App (at src/index.js:4)
Error I get when I try to add a new TodoItem:
AddTodo error
Error: Reference.set failed: First argument contains undefined in property 'todos.7cda085d-7653-4895-a140-d6f2629af9ca.id'
C:/Users/Paul/Desktop/react-todo/src/App.js:70
67 | title: title,
68 | completed: false
69 | }
> 70 | todosRef.set(newTodo).then(
^ 71 | this.setState({todos: [...this.state.todos, newTodo]})
72 | );
73 | };
262 |
263 | this.onSubmit = e => {
264 | e.preventDefault();
> 265 | this.props.addTodo(this.state.title);
266 | this.setState({
267 | title: ''
268 | });
onClick={this.props.delTodo.bind(this,id)}
You don't need to bind this here.
You just need to create an anonymous function and call your markComplete and delTodo function with given id in that anonymous function,
render() {
const { id, title } = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox" onChange={() => this.props.markComplete(id)} />{' '}
{ title }
<button className="delete" style={btnStyle} onClick={() => this.props.delTodo(id)}>x</button>
</p>
</div>
)
}
}

How to pass ref to component in React?

I am using a library called react-swipe (not especially relevant), which exposes next() and prev() methods on the instance, which I am accessing through a ref.
When I have the ReactSwipe component in my main App.js file this works perfectly well, e.g.:
_handlePrev() {
this.reactSwipe.prev()
}
_handleNext() {
this.reactSwipe.next()
}
render() {
let singlePlanets
singlePlanets = this.state.planetData.map(data => {
return (
<div className="single-planet" key={data.id}>
<div className="image">
<img src={emptyPlanet} alt={data.name} />
</div>
<h2>{data.name}</h2>
<div className="extract" dangerouslySetInnerHTML={{ __html: data.extract }} />
</div>
)
})
return (
<div className="app-container">
<TitleBar />
<ReactSwipe ref={reactSwipe => this.reactSwipe = reactSwipe} className="content" key={singlePlanets.length}>
{singlePlanets}
</ReactSwipe>
<MenuBar handleNext={this._handleNext.bind(this)} handlePrev={this._handlePrev.bind(this)} />
</div>
)
}
But what I'm trying to do is separate out the ReactSwipe and planetData mapping logic into its own component (code below), however when I do this (by trying to pass the ref through as a prop) I always get the error this.reactSwipe.prev() (or .next()) is not a function, no matter what I try. I'm wondering - what is the correct way to go about this?
This what I have in my return in App.js:
<PlanetInfo planetData={this.state.planetData} swipeRef={reactSwipe => this.reactSwipe = reactSwipe} />
and in PlanetInfo component:
return (
<ReactSwipe ref={this.swipeRef} className="content" key={singlePlanets.length}>
{singlePlanets}
</ReactSwipe>
)
Replace ref={this.swipeRef} with ref={this.props.swipeRef} in PlanetInfo component.

Showing JSON Data using fetch api and map in reactjs [duplicate]

This question already has answers here:
How can I return multiple lines JSX in another return statement in React?
(8 answers)
Closed 4 years ago.
I have been trying to using fetch api to display json and to use map to iterate the data, but I am stuck in displaying json and iterating it in reactjs
Here is the file
App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super();
this.state = {
productlist: [],
error: null,
}
}
componentDidMount(){
fetch(`http://texpertise.in/data.php`)
.then(result => result.json())
.then(productlist => this.setState({productlist: productlist.value}))
}
render() {
return (
<div>
{this.state.productlist.map(product =>
<div> {product.name} </div>
<div> {product.description} </div>
<div> {product.image} </div>
<div> {product.nonVeg} </div>
<div> {product.spicy} </div>
)}
</div>
);
}
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I am getting this error
./src/App.js
Syntax error: reactspa/src/App.js: Adjacent JSX elements must be wrapped in an enclosing tag (42:23)
40 | {this.state.productlist.map(product =>
41 | <div> {product.name} </div>
> 42 | <div> {product.description} </div>
| ^
43 | <div> {product.image} </div>
44 | <div> {product.nonVeg} </div>
45 | <div> {product.spicy} </div>
When using react 15.1, your .map can only return a single element, in your case, you are returning
<div>{product.name}</div> <div>{product.description}</div>
it should be wrapped and returned as a single element
<div><div>{product.name}</div> <div>{product.description}</div></div>

Any use of a keyed object should be wrapped in React.addons.createFragment(object)

let playerInfo = [{
name: 'jose',
country: 'USA',
age: 47
}];
export default class PlayersInfo extends React.Component {
static getProps() {
return {};
}
render() {
let playerInf = playerInfo.map((playerData, index) => {
return <div className="item" key={index}>{playerData}</div>
});
return <div>
<div>
{playerInf}
</div>
<RouteHandler />
</div>;
}
Why am I getting this error in the browser's console?
Warning: Any use of a keyed object should be wrapped in React.addons.createFragment(object) before being passed as a child.
I put together a JSBin for you. Basically, the warning comes from this line:
return <div className="item" key={index}>{playerData}</div>
It's because playerData is an object and ReactJS doesn't know how to render it.
If you change that to the following, it won't give you the warning:
return (
<div className="item" key={index}>
<div>Name: {playerData.name}</div>
<div>Country: {playerData.country}</div>
<div>Age: {playerData.age}</div>
</div>
);
Why am I getting this error in the browser's console?
Because you are passing an object (playerData) as child to a React Component.
I tend to run into this when rendering a Date object by mistake. You need to stringify these manually:
Produces warning:
<div>{ new Date() }</div>
Works ok:
<div>{ String(new Date()) }</div>
Does this work?
return <div>
<div>
{playerInf}
</div>
<div>
<RouteHandler />
</div>;
</div>
}

Categories

Resources