Trying to map over passed array of objects property in React - javascript

Started learning React and JS not too long ago.
I have a parent class 'App' that gets a an array of objects 'data' from data.js. App.js is sending that 'data' property down to the 'BookList' class. I am trying to map over said property in the BookList class and save elements in 'mappedBooks' but keep getting this error:
TypeError: this.props.books.map is not a function
DATA.JS:
const data = [
{
id: 1,
title: `The Pragmatic Programmer`,
author: `David Thomas, Andrew Hunt`,
img: `https://images-na.ssl-images-amazon.com/images/I/51cUVaBWZzL._SX380_BO1,204,203,200_.jpg`
},
{
id: 2,
title: `HTML and CSS: Design and Build Websites`,
author: `Jon Duckett`,
img: `https://images-na.ssl-images-amazon.com/images/I/31aX81I6vnL._SX351_BO1,204,203,200_.jpg`
},
{
id: 3,
title: `Coding All-in-one For Dummies`,
author: `Nikhil Abraham`,
img: `https://images-na.ssl-images-amazon.com/images/I/51RXaV0MGzL._SX397_BO1,204,203,200_.jpg`
},
{
id: 4,
title: `Learning React`,
author: `Alex Banks, Eve Porcello`,
img: `https://images-na.ssl-images-amazon.com/images/I/51FHuacxYjL._SX379_BO1,204,203,200_.jpg`
},
{
id: 5,
title: `Learning Web Design`,
author: `Jennifer Robbins`,
img: `https://images-na.ssl-images-amazon.com/images/I/51iVcZUGuoL._SX408_BO1,204,203,200_.jpg`
},
{
id: 6,
title: `JavaScript and JQuery: Interactive Front-End Web Development`,
author: `Jon Duckett`,
img: `https://images-na.ssl-images-amazon.com/images/I/41y31M-zcgL._SX400_BO1,204,203,200_.jpg`
},
{
id: 7,
title: `Head First JavaScript Programming`,
author: `Eric Freeman, Elisabeth Robson`,
img: `https://images-na.ssl-images-amazon.com/images/I/51qQTSKL2nL._SX430_BO1,204,203,200_.jpg`
},
{
id: 8,
title: `Learning Redux`,
author: `Daniel Bugl`,
img: `https://images-na.ssl-images-amazon.com/images/I/41gxBZ8GNpL._SX403_BO1,204,203,200_.jpg`
},
{
id: 9,
title: `Node.js 8 the Right Way`,
author: `Jim Wilson`,
img: `https://images-na.ssl-images-amazon.com/images/I/51t44mzlCaL._SX415_BO1,204,203,200_.jpg`
},
{
id: 10,
title: `PostgreSQL: Up and Running`,
author: `Regina Obe`,
img: `https://images-na.ssl-images-amazon.com/images/I/51FSjiYDfpL._SX379_BO1,204,203,200_.jpg`
},
{
id: 11,
title: `Fundamentals of Web Development`,
author: `Randy Connolly, Ricardo Hoar`,
img: `https://images-na.ssl-images-amazon.com/images/I/51xEzGTH6lL._SX402_BO1,204,203,200_.jpg`
},
{
id: 12,
title: `Web Design Playground`,
author: `Paul McFedries`,
img: `https://images-na.ssl-images-amazon.com/images/I/41-6F+RDbIL._SX258_BO1,204,203,200_.jpg`
}
]
export default data;
APP.JS
import React, {Component} from 'react'
import './App.css';
import BookList from './Components/BookList';
import Header from './Components/Header'
import Shelf from './Components/Shelf';
import data from './data'
class App extends Component {
constructor(){
super()
this.state ={
books : {data}
}
}
render(){
return (
<div className="App">
<Header/>
<BookList books={this.state.books}/>
<Shelf/>
</div>
);
}
}
export default App;
and BOOKLIST.JS:
import React, {Component} from 'react'
class BookList extends Component{
render(){
let mappedBooks = this.props.books.map(function(element){
return {element}
})
return(
<div className = 'BookList'>
<h1>list</h1>
</div>
)
}
}
export default BookList

in Data.js you've got
export default data;
and data is an array. So when you import data in App.js it's an array.
Try running this code snippet to get a better idea of what's going on here:
let data = [1,2,3,4]
console.log('when data is an array')
console.log('{data} is', {data})
data = {
'data': [1,2,3,4]
}
console.log('when data is an object with a property called data pointing to an array')
console.log('{data} is', {data})
console.log('but if you call a function and pass in data (as an object with data as a named property pointing to an array), you can use the curly braces to pull the array out of the object using destructuring')
function destructureData({data}) {
console.log(data)
}
destructureData(data)
So, when you do this:
this.state = {
books: { data }
}
it's actually an object property shorthand that's interpreted (in ES6) as this:
this.state = {
books: { data: data }
}
If you actually want this.state.books to be the array you imported from data.js then you set it directly:
this.state = {
books: data
}
Or, using that object property shorthand, and the fact that you've got data as a default export, you could change the import to this:
import books from './data'
And then do this:
this.state = {
books
}
If you'd like to read more about the destructuring syntax–where you use curly braces to pull data out of an object or an array–this article on MDN is a good read.

Related

How to store React component in object

I have an object of all apps:
export const allApps: appInterface[] = [
{
id: uuidv4(),
name: "Minesweeper",
icon: minesweeperIcon,
disabled: false,
},
];
I want to add component property to each object like this:
export const allApps: appInterface[] = [
{
id: uuidv4(),
name: "Minesweeper",
icon: minesweeperIcon,
disabled: false,
component: Minesweeper, //It is imported as import Minesweeper from ../Components/Minesweeper";
},
];
And then I want to display all components in App.js:
allApps.forEach((app)=>{
<div>{app.component}<div> // for eg: <div><Minesweeper/></div>
});
Is this possible to do?
Try as below code it should work.
allApps.map((app)=>{
const { component: Component } = app;
return <Component />;
});

Parsing error: Unexpected token, expected ";" whenever I add a constructor?

Whenever I try to add a class constructor it shows this error, Unexpected token, expected ";"
I'm trying to use a constructor to initialize the internal component state but keeps giving this error. I've tried rewriting the code but it didn't work.
Without the constructor everything works fine, Please what I'm I missing?
See the image of the error
I just started learning to react last week
import React, { Component } from 'react';
import './App.css';
function App() {
constructor(props) {
super(props);
this.state = {
list,
github
};
}
const list = [
{
title: 'React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://github.com/reactjs/redux',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
const github = [
{
id: 1,
name: 'Sanusi Hayatu',
username: 'hamicch',
url: 'https://github.com/hamicch',
repos: 24
},
{
id: 2,
name: 'Hayatu Michael',
username: 'Azeez',
url: 'https://github.com/azeez',
repos: 30
},
{
id: 3,
name: 'Ridwan Abdulahi',
username: 'ridwan',
url: 'https://github.com/ridwan',
repos: 50
}
];
return (
<div className="App">
{list.map(item =>
<div key={item.objectID}>
<span>
<a href={item.url}>{item.title}</a>
</span>:
<span>{item.author}</span>--
<span>{item.num_comments}</span>--
<span>{item.points}</span>
</div>
)}
<h2>GitHub Accounts</h2>
{github.map(user =>
<div key={user.id} class='acc'>
<div>
<strong>Username: </strong>
<a href={user.url}>{user.username}</a>
</div>
<div>
<strong>Name: </strong> {user.name}
</div>
<div>
<strong>Repos: </strong>
{user.repos}
</div>
</div>
)}
</div>
);
}
export default App;
This is the error page
You cannot have constructor in functional component, it is reserved for class components only.
The shorthand for property: a_function can only be used in an object literal or class.
You can't use it where an expression or statement is expected.
class Foo {
constructor() {
this.ran = Math.random();
}
}
const foo = new Foo();
const bar = new Foo();
console.log(foo.ran);
console.log(bar.ran);
You're writing a function component. There's no class, so you aren't constructing an object, so having a constructor doesn't make sense anyway.

Map stops iterating after first iteration in React

My component is supposed to retrieve the data for courses when the component mounts. The problem that I have is that whether I use the course Id or the course title as the key, I get the following error:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I have looked through the react docs, here on Stack Overflow, and tried different ways to get it to work. The only way I can get it to partially work is by adding an index as a parameter for map. When I use this method, I run into another problem and that is, it stops after the first iteration, even though there are 10 items. How can I fix this?
Here is my code:
CoursesPage.js
import React from 'react';
import { connect } from 'react-redux';
import * as courseActions from '../../redux/actions/courseActions';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
class CoursesPage extends React.Component {
componentDidMount() {
this.props.actions.loadCourses().catch(error => {
alert("Loading courses failed" + error);
});
}
render() {
return (
<>
<h2>Courses</h2>
{this.props.courses.map((course, index) => (
<div key={course[index].title}>{course[index].title}</div>
))}
</>
);
}
}
CoursesPage.propTypes = {
courses: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
}
function mapStateToProps(state) {
return {
courses: state.courses
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);
My mock data:
const courses = [
{
id: 1,
title: "Securing React Apps with Auth0",
slug: "react-auth0-authentication-security",
authorId: 1,
category: "JavaScript"
},
{
id: 2,
title: "React: The Big Picture",
slug: "react-big-picture",
authorId: 1,
category: "JavaScript"
},
{
id: 3,
title: "Creating Reusable React Components",
slug: "react-creating-reusable-components",
authorId: 1,
category: "JavaScript"
},
{
id: 4,
title: "Building a JavaScript Development Environment",
slug: "javascript-development-environment",
authorId: 1,
category: "JavaScript"
},
{
id: 5,
title: "Building Applications with React and Redux",
slug: "react-redux-react-router-es6",
authorId: 1,
category: "JavaScript"
},
{
id: 6,
title: "Building Applications in React and Flux",
slug: "react-flux-building-applications",
authorId: 1,
category: "JavaScript"
},
{
id: 7,
title: "Clean Code: Writing Code for Humans",
slug: "writing-clean-code-humans",
authorId: 1,
category: "Software Practices"
},
{
id: 8,
title: "Architecture Applications for the Real World",
slug: "architecting-applications-dotnet",
authorId: 1,
category: "Software Architecture"
},
{
id: 9,
title: "Becoming an Outlier: Reprogramming the Developer Mind",
slug: "career-reboot-for-developer-mind",
authorId: 1,
category: "Career"
},
{
id: 10,
title: "Web Component Fundamentals",
slug: "web-components-shadow-dom",
authorId: 1,
category: "HTML5"
}
];
const authors = [
{ id: 1, name: "Cory House" },
{ id: 2, name: "Scott Allen" },
{ id: 3, name: "Dan Wahlin" }
];
const newCourse = {
id: null,
title: "",
authorId: null,
category: ""
};
module.exports = {
newCourse,
courses,
authors
};
Edit:
I am using Redux Thunk.
Here is my actionType.js file:
export const CREATE_COURSE = "CREATE_COURSE";
export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS";
Here is my CourseActions.js file:
import * as types from './actionTypes';
import * as courseApi from "../../api/courseApi";
export function createCourse(course) {
return { type: types.CREATE_COURSE, course };
}
export function loadCourseSuccess(courses) {
return { type: types.LOAD_COURSES_SUCCESS, courses };
}
export function loadCourses() {
return function (dispatch) {
return courseApi.getCourses().then(courses => {
dispatch(loadCourseSuccess(courses));
}).catch(error => {
throw error;
})
}
}
Here is my courseReducer.js file:
import * as types from '../actions/actionTypes';
export default function courseReducer(state = [], action) {
switch (action.type) {
case types.CREATE_COURSE:
return [...state, { ...action.course }];
case types.LOAD_COURSES_SUCCESS:
return [...state, { ...action.courses }];
default:
return state;
}
}
Any help would be appreciated.
Thanks.
P.S. I know that you should use Id for key. But the way it has to be done for now is using the title of the course as the key.
With your edits, I think we can more effectively help you. For future, it would be beneficial to post an example of your code not working on https://codesandbox.io/
Also to help yourself out when you debug, isolate the react component from the use of redux. This will allow you to ensure your react component renders when given data, then focus on getting redux to provide you the data your react component has.
You can do this by first defining mockdata inside your react component, then moving that mock data to your reducer, then finally replacing the mock data with the live api call.
On to the code:
You have two issues: the first is that you want to index into the array courses but instead due to a typo, you are actually using the property accessor into the object course
key={course[index].title}
As your question states you must use the title as the key simply change the div to be:
<div key={course.title}>{course.title}</div> and your code should work as expected.
Once you have addressed that, then re-enable loading data from your API call using Redux, and you can address the issues with Redux.
Looking at your reducer, you have a clear bug, and depending on your use case, a potential bug:
case types.LOAD_COURSES_SUCCESS:
return [...state, { ...action.courses }];
action.courses is an array, and the code is creating a new array that contains all the elements of the previous array state and adding a new object, which contains the contents of a destructured array.courses array.
Which that does is effectively append a single object to your array, and the object is comprised of elements from your courses array. The item index becomes the key, and the item itself is the value.
You can visualize it here: https://codesandbox.io/s/divine-pond-nr3j8
Instead you want
return [...state, ...action.courses];
The second, potential bug, is that you are actually appending the results of the courses api. Which for subsequent calls to load courses, you will duplicate the data. That may be what you want, but I am going to assume that is not what you want.
So instead, your LOAD_COURSES_SUCCESS case should be rewritten to simply be:
return [...action.courses];
Since each course has a unique ID field, the problem could be solved by using the id as a key
render() {
return (
<>
<h2>Courses</h2>
{this.props.courses.map(course => (
<div key={course.id}>{course.title}</div>
))}
</>
);
}
The issue is here:
{this.props.courses.map((course, index) => (
<div key={course[index].title}>{course[index].title}</div>
))}
//course in itself is data
Solution:
{this.props.courses.map((course, index) => (
<div key={`${course.title}-${index}`}>{course.title}</div>
))}
Better way would always to take care unique id for key
Here's the code snippet for render method.
Give it a try.
*Update = use course title as key
render() {
const courseList = this.props.courses.map(course => (
<div key={course.title}></div>
));
return (
<>
<h2>Courses</h2>
<div>{courseList}</div>
</>
);
}
There are many ways you can pass the key when it is iterating...
var courses = this.state.courses.map(function(course, index) {
return(
<div key={index}>
<div key={course.title} id={course.title}>
<h2 key={"header"+course.title}>{course.title}</h2>
</div>
</div>
)
});
But you should try to pass a unique id rather than a string.

How to access properties of objects from a third-party DOM library used in a React component from the outside

I want to use Vis.js in a React based project. Since none of the Vis Network implementations work for me I have to use the plain library.
This is my test React component
import { DataSet, Network } from 'vis';
import React, { Component, createRef } from "react";
class VisNetwork extends Component {
constructor() {
super();
this.network = {};
this.appRef = createRef();
this.nodes = new DataSet([
{ id: 1, label: 'Node 1' },
{ id: 2, label: 'Node 2' },
{ id: 3, label: 'Node 3' },
{ id: 4, label: 'Node 4' },
{ id: 5, label: 'Node 5' }
]);
this.edges = new DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 }
]);
this.data = {
nodes: this.nodes,
edges: this.edges
};
this.options ={};
}
componentDidMount() {
this.network = new Network(this.appRef.current, this.data, this.options);
}
render() {
return (
<div ref={this.appRef} />
);
}
}
export default VisNetwork;
It's the only component mounted so far
ReactDOM.render(<VisNetwork />,document.getElementById('mynetwork'));
My question is how I can access the properties of the network, for example, to get or delete a node.
node = nodes.get(nodeId);
I read about React Ref and tried something like
() =>{ console.log(document.getElementsByClassName('vis-network')) as callback of ReactDOM.render()but that doesn't help.
Another question is why isn't the ref not set and it's just <div>.
Because I thought that this.appRef = createRef(); in the constructor of the component and ref={this.appRef} in render() would lead to a ref.
I hope you can give me a hint.
Actually the flow should be the other way round, define the nodes outside of the component, then pass them in. For that define the constructor as:
constructor(props) {
super(props);
this.nodes = props.nodes;
//...
}
Then construct it as:
const nodes = new DataSet([/*...*/]);
ReactDOM.render(<VisNetwork nodes={nodes} />,document.getElementById('mynetwork'));
Note that you should put anything stateful into this.state and use setState to mutate it.

React JS: Cannot read property [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 4 years ago.
I'm learning React; I have a such problem. When I call function "handleSearch" it crashes with TypeError: Cannot read property 'CONTACTS' of undefined on console.log(this.CONTACTS);
I define CONTACTS in constructior. And call method handleSearch() in onChange of input. Then the error occurs.
But everything is ok with this.CONTACTS.map()
import React, { Component } from 'react';
import Contact from './Contact';
import './Contacts.css';
class ContactList extends Component {
constructor() {
super();
this.CONTACTS = [
{
id: 1,
name: 'Alex',
phone: '777',
image: 'https://78.media.tumblr.com/2b5293dc3133abeab79aa74e151e74f9/tumblr_pa1h148ulz1rzp45wo1_75sq.jpg'
},
{
id: 2,
name: 'Ann',
phone: '888',
image: 'https://78.media.tumblr.com/2b5293dc3133abeab79aa74e151e74f9/tumblr_pa1h148ulz1rzp45wo1_75sq.jpg'
},
{
id: 3,
name: 'Zel',
phone: '999',
image: 'https://78.media.tumblr.com/2b5293dc3133abeab79aa74e151e74f9/tumblr_pa1h148ulz1rzp45wo1_75sq.jpg'
}
];
this.state = {
displayedContacts: this.CONTACTS
};
}
handleSearch(event) {
var query = event.target.value.toLowerCase();
console.log(this.CONTACTS);
}
render() {
return (
<div className="contacts">
<input type="text" className="search-field" onChange={ this.handleSearch }/>
<ul className="contacts-list">
{
this.CONTACTS.map(function(el) {
return <Contact
key={el.id}
name={el.name}
phone={el.phone}
image={el.image} />
})
}
</ul>
</div>
);
}
}
export default ContactList;
You have to use bind in the constructor. In your event handler this refers to the DOM node. Please add this line to the constructor:
this.handleSearch = this.handleSearch.bind(this)

Categories