This question already has answers here:
map function for objects (instead of arrays)
(39 answers)
Closed 2 years ago.
I am trying to map and object in React and keep getting the following error
"TypeError: Cannot read property 'map' of undefined"
My Expected Outcome
task-1
task-2
task-3
task-4
Code
import React, { Component } from 'react';
class MapEx extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
'task-1': { id: 'task-1', content: 'clean house' },
'task-2': { id: 'task-2', content: 'walk dog' },
'task-3': { id: 'task-3', content: 'Do pushups' },
'task-4': { id: 'task-4', content: 'have a drink' }
}
};
}
render() {
const tasks = this.state.tasks
console.log(tasks)
return (
<div>
<h1>Hello</h1>
<p> {this.tasks.map((task) =>
task.id)}</p>
</div>
);
}
}
export default MapEx;
Two issues:
You reference this.tasks instead of this.state.tasks.
You are using map on an object instead of an array.
Try something like this:
return (
<div>
<h1>Hello</h1>
{Object.values(this.state.tasks).map(task => <p>{task.id}</p>)}
</div>
);
map can only be used on arrays. To begin with, convert your data to array DS and proceed as below.
import React, { Component } from 'react';
class MapEx extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
'task-1': { id: 'task-1', content: 'clean house' },
'task-2': { id: 'task-2', content: 'walk dog' },
'task-3': { id: 'task-3', content: 'Do pushups' },
'task-4': { id: 'task-4', content: 'have a drink' }
}
};
}
render() {
const tasks = this.state.tasks
console.log(tasks)
return (
<div>
<h1>Hello</h1>
{Object.values(tasks).map(task => (<p>{task.id}</p>))}
</div>
);
}
}
export default MapEx;
You can do something like this:
Destructuring state
As tasks is an object you can't map over it, you need to use object.keys
import React, { Component } from 'react';
class MapEx extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
'task-1': { id: 'task-1', content: 'clean house' },
'task-2': { id: 'task-2', content: 'walk dog' },
'task-3': { id: 'task-3', content: 'Do pushups' },
'task-4': { id: 'task-4', content: 'have a drink' }
}
};
}
render() {
const {tasks} = this.state
console.log(tasks)
return (
<div>
<h1>My tasks</h1>
{!!tasks ? Object.values(tasks).map(task => (<p>{task.id}</p>)) : null}
</div>
);
}
}
export default MapEx;
Working example on https://codesandbox.io/s/react-boilerplate-r68kh
I suggest you to read the docs of map.
It works with arrays and not objects.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
tasks is an object, you need to convert it into an array to make this work.
this.state = {
tasks: [
{ id: 'task-1', content: 'clean house' },
{ id: 'task-2', content: 'walk dog' },
{ id: 'task-3', content: 'Do pushups' },
{ id: 'task-4', content: 'have a drink' }
]
};
You can map over an array and here tasks is an object
Related
I can't pass my data from the fakeGenreService.js via array.
Please check the screenshot for the data rendered.
You will see that all things are being rendered, just not (the movie Title, Genre, Stock and Rate) which are available in the fakeGenreService.js
Please do let me know where I am going wrong??????
PLEASE DO LET ME KNOW WHY MY DATA IS NOT BEING RENDERED AND WHAT I NEED TO MAKE THE CHANGES IN THE CODE
I WILL REALLY APPRECIATE YOUR HELP!!!!!!
I am uploading my three files below
App.js
fakeGenreService.js
movies.js
Please check if I am passing the array correctly in the state block?????``
Here is App.js
http://prnt.sc/olccj9
Here is fakegenreService.js
http://prnt.sc/olcdr5
Here is movies.js
http://prnt.sc/olce2x
Here is the final result for the developmentserver
http://prnt.sc/olcejx
Tried various troubsleshooting steps for the array function
This part deals with App.js
import React, { Component } from "react";
import React, { Component } from "react";
import Movies from "./components/movies";
import "./App.css";
class App extends Component {
render() {
return (
<main className="container">
<Movies />
</main>
);
}
}
export default App;
This part is for movies.js
import React, { Component } from "react";
import { getMovies } from "../services/fakeMovieService";
class Movies extends Component {
constructor(props) {
super(props);
this.state = {
movies: [getMovies()]
};
}
handleDelete = movie => {
console.log(movie);
};
render() {
return (
<table className="table">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th />
</tr>
</thead>
<tbody>
{this.state.movies.map(movie => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>
<td>
<button
onCick={() => this.handleDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default Movies;
Here is fakeMovieService.js
import * as genresAPI from "./fakeGenreService";
const movies = [
{
_id: "5b21ca3eeb7f6fbccd471815",
title: "Terminator",
genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
numberInStock: 6,
dailyRentalRate: 2.5,
publishDate: "2018-01-03T19:04:28.809Z"
},
{
_id: "5b21ca3eeb7f6fbccd471816",
title: "Die Hard",
genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
numberInStock: 5,
dailyRentalRate: 2.5
},
{
_id: "5b21ca3eeb7f6fbccd471817",
title: "Get Out",
genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
numberInStock: 8,
dailyRentalRate: 3.5
},
{
_id: "5b21ca3eeb7f6fbccd471819",
title: "Trip to Italy",
genre: { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
numberInStock: 7,
dailyRentalRate: 3.5
},
{
_id: "5b21ca3eeb7f6fbccd47181a",
title: "Airplane",
genre: { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
numberInStock: 7,
dailyRentalRate: 3.5
},
{
_id: "5b21ca3eeb7f6fbccd47181b",
title: "Wedding Crashers",
genre: { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
numberInStock: 7,
dailyRentalRate: 3.5
},
{
_id: "5b21ca3eeb7f6fbccd47181e",
title: "Gone Girl",
genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
numberInStock: 7,
dailyRentalRate: 4.5
},
{
_id: "5b21ca3eeb7f6fbccd47181f",
title: "The Sixth Sense",
genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
numberInStock: 4,
dailyRentalRate: 3.5
},
{
_id: "5b21ca3eeb7f6fbccd471821",
title: "The Avengers",
genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
numberInStock: 7,
dailyRentalRate: 3.5
}
];
export function getMovies() {
return movies;
}
export function getMovie(id) {
return movies.find(m => m._id === id);
}
export function saveMovie(movie) {
let movieInDb = movies.find(m => m._id === movie._id) || {};
movieInDb.name = movie.name;
movieInDb.genre = genresAPI.genres.find(g => g._id === movie.genreId);
movieInDb.numberInStock = movie.numberInStock;
movieInDb.dailyRentalRate = movie.dailyRentalRate;
if (!movieInDb._id) {
movieInDb._id = Date.now();
movies.push(movieInDb);
}
return movieInDb;
}
export function deleteMovie(id) {
let movieInDb = movies.find(m => m._id === id);
movies.splice(movies.indexOf(movieInDb), 1);
return movieInDb;
}
The result of the data being rendered is shown here:
http://prnt.sc/olcejx
Please let me know how could the movies defined in getMovies() function coud be rendered in the table.
The issue seems to be here. getMovies would already return an array. You're wrapping it inside another one. Here, in yout Movies Component class, change it to just the function call:
constructor(props) {
super(props);
this.state = {
movies: getMovies() // [getMovies()]
};
}
You wrap the movies array into a second array. That does not work. You should write it like this :
this.state = {
movies: getMovies()
};
getMovies() already returning array. You are calling that function inside an array. so movies have an array of array. like this movies: [[datas]].
In movies.js file do this changes in the constructor. It should work.
this.state = {
movies: getMovies();
}
1.vue.js problem component
i want insert a new message in new array with method on click event but
not work for me because function is incomplete
where is the problem.
help me please.
<div class="col-lg-12">
<h1>{{message.title}}</h1>
<h4>{{message.subtitle}}</h4>
</p> {{message.body}}</p>
<button v-on:click="newMessage">Reverse Message</button>
</div>
import {
VueTabs,
VTab
}
from "vue-nav-tabs";
import "vue-nav-tabs/themes/vue-tabs.css";
export default {
components: {
VueTabs,
VTab
},
data() {
return {
title: "elenco",
messages: [{
id: 1,
title: "titolo",
subtitle: "sottotitolo",
body: "argomento",
author: "Amedeo",
date: "17/07/2017",
files: [{
id: 1,
title: "Allegatoriunione",
openfile: "Allegato.pdf"
}, ],
methods: {
newMessage: function() {
this.message.title = this.message.title
.push("")
.split("")
.reverse()
.join("");
}
Your Code Contains many syntax errors which probably fails silently.
Try this new updated code:
<script>
import { VueTabs, VTab } from 'vue-nav-tabs'
import 'vue-nav-tabs/themes/vue-tabs.css'
export default {
components: { VueTabs, VTab },
data() {
return {
title: 'elenco',
messages: [
{
id: 1,
title: 'titolo',
subtitle: 'sottotitolo',
body: 'argomento',
author: 'Amedeo',
date: '17/07/2017',
files: [
{
id: 1,
title: 'Allegatoriunione',
openfile: 'Allegato.pdf'
}
]
}
]
}
},
methods: {
newMessage() {
this.message.title = this.message.title
.push('')
.split('')
.reverse()
.join('')
}
}
}
</script>
I have 2 buttons which when clicked should filter by novelty or offer , I am able to make it so that when novelty is clicked it will filter by this but I am unable to make it so that if both are click it will filter by both novelty and offer
How can I make it so that when both novelty and offer are clicked it will filter by both of these?
https://www.webpackbin.com/bins/-KpVGNEN7ZuKAFODxuER
import React from 'react'
export default class extends React.Component {
constructor() {
super()
this.state = {
products: [
{ id: 1, novelty: true, offer: false, name: 'test1' },
{ id: 2, novelty: true, offer: true, name: 'test2' },
{ id: 3, novelty: false, offer: true, name: 'test3' }
],
display: 'all',
filters: [
{novelty:'true'},
{offer: 'true'}
]
}
}
setCategory (category) {
this.setState({
display: category
});
}
render() {
return(
<div>
<button onClick={()=>this.setCategory(true)}>Akce</button>
<button onClick={()=>this.setCategory(true)}>Offer</button>
{
this.state.products.filter( product =>
products.offer === this.state.display ||
this.state.display==='all')
.map(product =>
<div>{product.name}</div>
)
}
</div>
)
}
}
Here is the final version I've come up with:
import React from 'react'
export default class extends React.Component {
constructor() {
super()
this.state = {
products: [
{ id: 1, novelty: true, offer: false, name: 'test1' },
{ id: 2, novelty: true, offer: true, name: 'test2' },
{ id: 3, novelty: false, offer: true, name: 'test3' }
],
filters: {
novelty: true,
offer: true
}
}
}
setCategory (category) {
this.setState((state) => ({
filters: Object.assign({}, state.filters, { [category]: !state.filters[category] })
}));
}
render() {
console.log(this.state.filters)
return(
<div>
<button onClick={()=>this.setCategory('novelty')}>Akce</button>
<button onClick={()=>this.setCategory('offer')}>Offer</button>
{ this.state.products
.filter(product => product.novelty === this.state.filters.novelty || product.offer === this.state.filters.offer)
.map(product =>
<div key={product.id}>{product.name}</div>
)}
</div>
)
}
}
https://www.webpackbin.com/bins/-KpVHqfkjeraq6pGvHij
A few things:
Using a boolean instead of a string in your case is more adapted. (true instead of 'true').
display: 'all' isn't required for your use case. You can compute this value from your filters if you need to.
setCategory receive which category you want to set as a param.
I would rename setCategory to setFilter
Also, I'm using the asycnhronous version of setState. This allows you to hand in a function.
this.setState((state) => ({
filters: Object.assign({}, state.filters, { [category]: !state.filters[category] })
}));
Here I'm using Object.assign to create a new Object. I populate him with state.filters and finally I update the filter you want to.
category will either be novelty or offer and thanks to that I'm using the shorthand version of [category].
To conclude, I also update your filter function to check the product.novelty against the filter.novelty or the product.offer with the filter.offer
I am rendering a nested comment tree, I am not able to figure how to update the tree data present in the comment container from one of the deeply nested comments. I have created a basic fiddle representing the scenario, check it out HERE. The button in each node is supposed to invert the value of "likes" property, which should be coming from the state in parent container. Looking for a non redux solution.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
tree: [
{
id: "cake",
likes: true,
kids: [
{
id: "banana",
likes: false,
kids: []
},
{
id: "rocket",
likes: false,
kids: [
{
id: "homework",
likes: true,
kids: [
{
id: "something",
likes: true,
kids: []
}
]
}
]
}
]
}
]
};
}
render() {
return (
<div>
{this.state.tree.map(value => <Kid value={value} />)}
</div>
);
}
}
class Kid extends React.Component {
render() {
return (
<div style={{margin: '20px', border: '5px dashed #DCEDC8'}}>
<span>id: {this.props.value.id}</span><br />
<span>likes: {this.props.value.likes ? 'yes' : 'no'}</span><br />
<button>invert</button>
{this.props.value.kids.length
? this.props.value.kids.map(value => <Kid value={value} />)
: null}
</div>
);
}
}
React.render(<Parent />, document.getElementById("container"));
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
The recommendation for Redux state is to keep it normalized, something like this:
this.state = {
normalized: [
cake: {likes: true, kids: ["banana", "rocket"]}
banana: {likes: false, kids: []},
rocket: {likes: false, kids: ["homework"]},
homework: {likes: true, kids: ["something"]},
something: {likes: true, kids: []},
],
root: "cake",
};
Then, if you have an id, you would refer to an item with this.state.normalized[id]
For example, to traverse the tree and apply a function to every node, you would do:
function traverse(node, func) {
func(node);
for (let i = 0, len = node.kids.length; i < len; i++) {
traverse(this.state.normalized[node.kids[i]]);
}
}
traverse(this.state.normalized[this.state.root]);
Normalizr can be useful to normalize nested API responses if you don't want to code your own solution.
If you really want to keep an immutable tree in your state, Immutable.js is good at letting your performantly alter a tree in a single line without mutating it.
Here is the final code that does what you wanted.
There are a lot of things here that you need to pay attention to be able to understand how it works.
While rendering each of the kid, the parent path and kid's id becomes a kid's path. When the button is clicked this path gets passed to the Parent component where the state gets manipulated using this path.
This assumes that id will be unique for siblings.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
tree: [
{
id: "cake",
likes: true,
kids: [
{
id: "banana",
likes: false,
kids: []
},
{
id: "rocket",
likes: false,
kids: [
{
id: "homework",
likes: true,
kids: [
{
id: "something",
likes: true,
kids: []
}
]
}
]
}
]
}
]
};
this.onInvertHandler = this.onInvertHandler.bind(this);
}
onInvertHandler(path) {
this.setState((prevState) => {
const pathKeys = path.split(':');
let obj = prevState['tree'];
for(let i=0;i<pathKeys.length;i++) {
obj = i >0 ? obj.kids : obj;
obj = obj.find((el)=>{ return el.id == pathKeys[i] });
}
obj['likes'] = !obj['likes'];
return prevState;
})
};
render() {
return (
<div>
{this.state.tree.map(value => <Kid value={value} path = {value.id} onInvertHandler={this.onInvertHandler}/>)}
</div>
);
}
}
class Kid extends React.Component {
constructor(props){
super(props);
this.onInvertHandler = this.onInvertHandler.bind(this);
}
onInvertHandler(p) {
let params = typeof p === 'string' ? p : this.props.path;
this.props.onInvertHandler(params);
};
render() {
return (
<div style={{margin: '20px', border: '5px dashed #DCEDC8'}}>
<span>id: {this.props.value.id}</span><br />
<span>likes: {this.props.value.likes ? 'yes' : 'no'}</span><br />
<button onClick = {this.onInvertHandler}>invert</button>
{this.props.value.kids.length
? this.props.value.kids.map(value => <Kid value={value} path = {this.props.path + ':'+value.id} onInvertHandler={this.onInvertHandler}/>)
: null}
</div>
);
}
}
React.render(<Parent />, document.getElementById("container"));
So i have this REACT code which should work :
App.jsx:
class App extends Component {
constructor(props){
super(props);
this.state = {count: props.initialCount};
this.onChange = this.onChange.bind(this);
this.state = {
questions:[
{
id: 1,
text: 'What is your name ?',
choices: [
{
id: 'a',
text: 'Michael'
},
{
id: 'b',
text: 'Brad'
},
{
id: 'c',
text: 'Steven'
}
],
correct: 'b'
},
{
id: 2,
text: 'What is your mothers name ?',
choices: [
{
id: 'a',
text: 'Sara'
},
{
id: 'b',
text: 'Sue'
},
{
id: 'c',
text: 'Donna'
}
],
correct: 'c'
},
{
id: 3,
text: 'What is your father name ?',
choices: [
{
id: 'a',
text: 'Bobby'
},
{
id: 'b',
text: 'Harry'
},
{
id: 'c',
text: 'Wayne'
}
],
correct: 'a'
},
{
id: 4,
text: 'What is your friend name ?',
choices: [
{
id: 'a',
text: 'John'
},
{
id: 'b',
text: 'Paul'
},
{
id: 'c',
text: 'Dan'
}
],
correct: 'c'
}
],
score: 0,
current: 1
}
}
render(){
return(
<div onClick={this.onChange}>
<QuestionList {...this.state} />
</div>
)
}
}
export default App
QuestionList.jsx :
class QuestionList extends Component {
render(){
return(
<div className="questions">
{
this.props.questions.map(question => {
return <Question question={question} key={question.id} {...this.props} />
})
}
</div>
)
}
}
export default QuestionList
Question.jsx :
class Question extends Component {
render(){
const {question} = this.props;
return(
<div className="well">
<h3 >{question.text}</h3>
<hr />
<ul className="list-group">
{
this.props.question.choices.map(choice => {
return(
<li className="list-group-item">
{choice.id} <input type="radio" name={question.id} value={choice.id} /> {choice.text}
</li>
)
})
}
</ul>
</div>
)
}
}
export default Question
except it throws this error in console : TypeError: _this.onChange is undefined
I google it and found out that :
Make sure you do this.onChange = this.onChange.bind(this) in your constructor
But when I add that on my controller i get this error : TypeError: this.onChange is undefined
class Question extends Component {
onChange(e) {
//Your change code here!!
}
render(){
const {question} = this.props;
return(
<div className="well">
<h3 >{question.text}</h3>
<hr />
<ul className="list-group">
{
this.props.question.choices.map(choice => {
return(
<li className="list-group-item">
{choice.id} <input type="radio" onChange={this.onChange.bind(this)} name={question.id} value={choice.id} /> {choice.text}
</li>
)
})
}
</ul>
</div>
)
}
}
export default Question
You should have a onChange method in your class.
If you want to put it in the constructor you need to do it like this:
Note: this is an example from the react docs, which are linked below
export class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
this.onChange = this.onChange.bind(this);
}
onChange() {} // add this
render() {
return (
<div onClick={this.onChange}>
Clicks: {this.state.count}
</div>
);
}
}
docs: https://facebook.github.io/react/docs/reusable-components.html#es6-classes
To avoid an error with binding this, you have two options:
Using this.onClick = this.onClick.bind(this); (inside constructor)
Using arrow function for onClick function
I hope this trick helps you.