How to load firestore subcollection to redux store? - reactjs

I'm having trouble loading firestore subcollection in redux store. I'm trying to load respective subcollection inside the child component.
The component structure looks like this:
Dashboard
--Project list
--project summary
--comments
I have loaded collection named Projects using firestoreConnect inside Dashboard component passing data to Project list mapping data to Project Summary. comments is a child component of project summary
My firestore collection structure looks like this:
Projects
--project1
--comments
Dashboard:
class Dashboard extends Component {
render() {
const { projects, auth, notifications } = this.props
if (!auth.uid) return <Redirect to='/signin' />
return(
<div className="dashboard container">
<div className="row">
<div className="col s12 m8 offset-m2 l8">
<ProjectList projects={projects} />
</div>
<div className="col s12 offset-m1 hide-on-med-and-down l4">
<Notification notifications={notifications} />
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
// console.log("ref: ",state)
return {
projects: state.firestore.ordered.projects,
initials: state.firebase.profile.initials,
auth: state.firebase.auth,
notifications: state.firestore.ordered.notifications
}
}
export default compose (
connect(mapStateToProps),
firestoreConnect([
{ collection: 'projects', orderBy: ['createdAt', 'desc'] },
{ collection: 'notifications', limit: 3, orderBy: ['time', 'desc'] }
])
)(Dashboard)
Project list Component:
const ProjectList = ({ projects }) => {
return(
<div className="project-list section">
{projects && projects.map((project) => {
return(
<div key={project.id}>
<ProjectSummary project={project}/>
</div>
)
})}
</div>
)
}
export default ProjectList
Project summry:
const ProjectSummary = ({ project }) => {
return(
<div className="card z-depth-0 project-summary show-up post">
<div className="name">
<div className="">
<span className="btn btn-floating z-depth-0 black user-indicator">
{project.authorFirstName && project.authorFirstName[0]}{project.authorSecondName && project.authorSecondName[0]}
</span>
<span> {project.authorFirstName {project.authorSecondName} </span>
<span className="right options"> <i className="material-icons">more_vert</i> </span>
</div>
</div>
<div className="card-image">
<img src={project.imageUrl} alt="art" />
<PostCredits />
</div>
<div className="card-reveal">
<Comments id={project.id} />
</div>
<div className="card-content">
<Link to={'/projectdetails/' + project.id} className="black-text">
<p className="post-title"> {project.title} </p>
</Link>
<p className="grey-text lighten-3 load-comments activator"> Load comments </p>
<p className="grey-text date-format">
{project.createdAt && project.createdAt
.toDate()
.toLocaleDateString('indian', {
year: "numeric", month: "short", day: "numeric"
})}
</p>
</div>
<div className="add-comment">
<AddComment projectId={project.id} />
</div>
</div>
)
}
export default ProjectSummary
Comment:
const Comment = (props) => {
return(
<div>
<div className="loaded-comments">
<span className="card-title grey-text text-darken-4"> Comments <i className="material-icons right">close</i> </span>
<p> <span className="commentor"> Joe </span> </p>
</div>
</div>
)
}
const mapStateToProps = (state, ownProps) => {
console.log("state: ", state)
return {
}
}
export default compose (
connect(mapStateToProps),
firestoreConnect(props => {
return [
{ collection: 'projects',
doc: props.id,
subcollections: [
{
collection: 'comments'
}
]
}
]
})
)(Comment)
Expected Result:
*Want to load Project collection's subcollection named Comments
Actual Result:
*No error occurs but still no data is loaded.
Thanks in advance!

There is a possibility that you just don't see that sub-collection.
If you look under in your browser console, navigate to firebase > ordered > prjoects > comments. you should be able to see an array of your comment
Example:
|-firestore
|--ordered:
|---projects: Array(1)
|----0:
|-----id: *uid*
|------comments: Array(2)
|-------0: {*comment details*}
|-------1: {*comment details*}
|------length: 2
|-----__proto__: Array(0)
After you can map your data based on your key.
const mapStateToProps = (state) => {
return {
projects: state.firestore.ordered.projects.comments,
}
}
Once connected you will be able to use your data.

Related

Button Click to Render Div

I want to click the button and render its corresponding div. Should I add the the div's info that I want to render to the state?
I'm sure there's a few different ways to solve this but I want to do it the React way and maybe as a function component?
Updated
export default function About(props) (
const [isHidden, setIsHidden] = useState(true);
const handleClick = () => {
setIsHidden(!isHidden);
};
return (
<div className="row justify-content-md-center">
<div className="col-auto">
<CustomButton onClick={handleClick}>Website Develpment</CustomButton>
</div>
<div className="col-auto">
<CustomButton onClick={handleClick}>Wordpress Develpment</CustomButton>
</div>
<div className="col-auto">
<CustomButton onClick={handleClick}>Ecommerce Development</CustomButton>
</div>
</div>
<div className="row">
<div className="col-md-4">column 1</div>
<div className="col-md-4">column 2</div>
<div className="col-md-4">column 3</div>
</div>
);
)
You can store an ID as a string state variable, and set that variable on button press.
Then use ConditionalRendering to only display the div with the matching ID.
const {useState} = React;
function About(props) {
const [visibleItem, setVisibleItem] = useState('');
return (
<div>
<button onClick={() => setVisibleItem("website")}>
Show Website Develpment
</button>
<button onClick={() => setVisibleItem("wordpress")}>
Show Wordpress Develpment
</button>
<button onClick={() => setVisibleItem("ecommerce")}>
Show Ecommerce Development
</button>
{visibleItem === "website" &&
<div>
<h2>Wordpress Development</h2>
<p>Info about Wordpress and all the things I can do with it</p>
</div>
}
{visibleItem === "wordpress" &&
<div>
<h2>Ecommerce Development</h2>
<p>I can do eccomerce things too</p>
</div>
}
{visibleItem === "ecommerce" &&
<div>
<h2>Website Development</h2>
<p>Info about Webdev</p>
</div>
}
</div>
);
}
ReactDOM.render(
<About/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
If the sections were much bigger then I'd recommend splitting them out into separate components, or maybe doing an if or switch statement to select between them before the return, but the above snippet is a pattern I have used often with react.
You can create two states one holding array of button info to minimize repetition of code and another state to track the column clicked.
import React, { useState } from "react";
export default function About(props) {
const [colNum, setColNum] = useState('');
const [btnNames] = useState([
{
colId: 1,
name: "Website Develpment"
},
{
colId: 2,
name: "Wordpress Develpment"
},
{
colId: 3,
name: "Ecommerce Develpment"
}
]);
const handleClick = (id) => {
setColNum(id);
};
return (
<>
<div className="row justify-content-md-center">
{btnNames.map(element => (
<div className="col-auto" key={element.colId}>
<CustomButton onClick={()=> handleClick(element.colId)}>{element.name}</CustomButton>
</div>
))}
</div>
{colNum !== '' ? (<div className="row">
<div className="col-md-4">column {colNum}</div>
</div>) : null }
</>
);
}
So you can define showing value for each section in state, which initial should be false. Then we can handle this state with a function where built-in JS tools can be used (Object.entries, Object.fromEntries and map). The element will be displayed, the value of which will be true, and the rest will automatically become false. More details can be found here: https://codesandbox.io/s/happy-sea-nckmw?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
import styles from "./TestStyles.module.css";
import CustomButton from "./CustomButton";
export default function App() {
const [showDiv, setShowDiv] = useState({
web: false,
wordpress: false,
ecommerce: false
});
const showDivHandler = (val) => {
const newState = Object.fromEntries(
Object.entries(showDiv).map(([key, value]) => [key, (value = false)])
);
setShowDiv({
...newState,
[val]: !showDiv[val]
});
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div className={styles.Section}>
<div className="col-auto">
<CustomButton clicked={() => showDivHandler("web")}>
Website Develpment
</CustomButton>
</div>
<div className="col-auto">
<CustomButton clicked={() => showDivHandler("wordpress")}>
Wordpress Develpment
</CustomButton>
</div>
<div className="col-auto">
<CustomButton clicked={() => showDivHandler("ecommerce")}>
Ecommerce Development
</CustomButton>
</div>
</div>
<div className={styles.Section} style={{marginTop: 20}}>
{showDiv.web && <div className={styles.Content}>column 1</div>}
{showDiv.wordpress && <div className={styles.Content}>column 2</div>}
{showDiv.ecommerce && <div className={styles.Content}>column 3</div>}
</div>
</div>
);
}
The custom button can look like this:
import React from "react";
const button = (props) => (
<button
onClick={props.clicked}
//some other props if need it(e.g. styles)
>
{props.children}
</button>
);
export default button;
This is how I solved it. Thanks for your help!
export default function About() {
const [visibleItem, setVisibleItem] = useState(1);
const [priceItems] = useState([
{
id: 1,
name: "website",
},
{
id: 2,
name: "wordpress",
},
{
id: 3,
name: "ecommerce",
},
]);
const handleClick = (id) => {
setVisibleItem(id);
};
return (
<div className="container-fluid pricing-wrapper">
<div className="row">
<div className="col">
<h1>Pricing</h1>
</div>
</div>
<div className="row">
<div className="col">
<p>Innovation that empowers your team while also reducing risk!</p>
<div className="seperator"></div>
</div>
</div>
<div className="row justify-content-md-center">
<div className="row justify-content-md-center">
{priceItems.map((item) => (
<div className="col-auto">
<CustomButton onClick={() => setVisibleItem(item.id)}>
{item.name}
</CustomButton>
</div>
))}
</div>
</div>
{priceItems
.filter((item) => item.id === visibleItem)
.map((item) => (
<PriceItem item={item} />
))}
</div>
);
}

Loop through array in React and create elements (not list items) from it

I'm learning React and have done a fair bit of research on this. I've quickly discovered that the map() function is what I think I should be using for looping through an array.
But, my problem is all the examples in the React documentation and in the SO questions I've viewed use <ul> and <li> HTML elements to handle the output.
I'm not sure that my use case is "correct" as far as React structure is concerned, but, I want to output a <div> with some child elements each time I loop through.
Here is my static code so far:
const Comment = () => {
return (
<div className="commenter">
<div className="commenter-inner">
<div className="commenter-left">
<img src={chachi}/>
<p className="commenter-name">Scott Baio</p>
</div>
<div className="commenter-comment">
<p>Ehhhh!! Joanie loves Chachi!!!</p>
</div>
</div>
</div>
)
}
This works, but now if I have additional comments I want to be able to serve up the same block of code again but with the new commenters name, image, comment content etc.
So I've now made an array to house my multiple commenters, and things aren't really working anymore.
import React, { Component } from 'react'
import fonzie from "./img/the-fonz.jpg";
import chachi from "./img/chachi.jpg";
const Comments = [
{
id: 1,
name: 'Hello World',
photo: fonzie,
comment: 'Welcome to learning React!'
},
{
id: 2,
name: 'Hello World',
photo: chachi,
comment: 'Welcome to learning React!'
}
];
const commentEngine = props.Comments.map((comment) =>
<div className="commenter" key={comment.id}>
<div className="commenter-inner">
<div className="commenter-left">
<img src={comment.photo}/>
<p className="commenter-name">{comment.name}</p>
</div>
<div className="commenter-comment">
<p>{comment.comment}</p>
</div>
</div>
</div>
);
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
<commentEngine />
</div>
);
}
}
export default Comments
At this point I'm unsure how to verify if my loop is working in the first place and how to get the output properly displaying in my app.
Any help is greatly appreciated, as is insight into whether or not this is well structured or should be separate components.
Thanks!
It sounds like you want to re-use the Comment component with data passed by Comments. In React, this is done via props.
So, you'll want to pass the images's src, the name, and the description:
const comments = [
{
id: 1,
name: "Hello World",
photo: fonzie,
comment: "Welcome to learning React!",
},
{
id: 2,
name: "Hello World",
photo: chachi,
comment: "Welcome to learning React!",
},
];
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{comments.map((comment) => {
return (
<Comment
key={comment.id} // https://reactjs.org/docs/lists-and-keys.html
name={comment.name}
imgSrc={comment.photo}
comment={comment.comment}
/>
);
})}
</div>
);
}
}
Notice that I've renamed the constant array Comments to comments so that the name doesn't clash with the Comments component.
Then in the Comment component, you can access these props via the argument passed to the function component:
const Comment = (props) => {
return (
<div className="commenter">
<div className="commenter-inner">
<div className="commenter-left">
<img src={props.imgSrc} />
<p className="commenter-name">{props.name}</p>
</div>
<div className="commenter-comment">
<p>{props.comment}</p>
</div>
</div>
</div>
);
};
Additionally, we can make the code a bit less verbose by leveraging object destructuring:
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{comments.map(({ id, name, photo, comment }) => {
return (
<Comment key={id} name={name} imgSrc={photo} comment={comment} />
);
})}
</div>
);
}
}
// ...
const Comment = ({ imgSrc, name, comment }) => {
return (
<div className="commenter">
<div className="commenter-inner">
<div className="commenter-left">
<img src={imgSrc} />
<p className="commenter-name">{name}</p>
</div>
<div className="commenter-comment">
<p>{comment}</p>
</div>
</div>
</div>
);
};
const commentEngine = (comments) => {
return comments.map((comment)=>{
return (
<div className="commenter" key={comment.id}>
<div className="commenter-inner">
<div className="commenter-left">
<img src={comment.photo}/>
<p className="commenter-name">{comment.name}</p>
</div>
<div className="commenter-comment">
<p>{comment.comment}</p>
</div>
</div>
</div>
)})
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{commentEngine(props.Comment)}
</div>
);
}
}
Now when you render Comments you need to pass the Comment props.
<Comments Comment={Comments}/>
USAGECASE
import React, { Component } from 'react'
import fonzie from "./img/the-fonz.jpg";
import chachi from "./img/chachi.jpg";
const Comments = [
{
id: 1,
name: 'Hello World',
photo: fonzie,
comment: 'Welcome to learning React!'
},
{
id: 2,
name: 'Hello World',
photo: chachi,
comment: 'Welcome to learning React!'
}
];
const Comment = props =>
const {comment} = props;
<div className="commenter" key={comment.id}>
<div className="commenter-inner">
<div className="commenter-left">
<img src={comment.photo}/>
<p className="commenter-name">{comment.name}</p>
</div>
<div className="commenter-comment">
<p>{comment.comment}</p>
</div>
</div>
</div>
);
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{Comments.map((comment,index) => <Comment key={'[CUSTOM_KEY]'} props={comment}> )}
</div>
);
}
}
export default Comments
ANSWER
First of all, You can use index parameter in Array.map
Secondly, if you want to use list component you can make Single Component like <Comment comment={comment}> and you can use it with Array.map
And It is very good to study How to make functional component

Passing list of values as parameters in reactJS

I am new to react js and I need help..
I am trying to create a page which shows details of a team
import React from "react";
import "../styles/team.css";
import bs from "../images/girl.jpg";
import { membercard } from "./teamcard";
export const Team = () => {
const user = {
name: "sowmi",
email: "sddsd",
src: { bs },
designation: "sgsfgsfg",
};
return (
<section className="team">
<div className="container text-center">
<h1 className="py-3">MEET OUR AMAZING TEAM</h1>
<p className="p1">
This the paragraph where you can write more details about your team
</p>
<div className="row">
<membercard {...user} />
</div>
</div>
</section>
);
};
This is my Team.js file
import React from "react";
import "../styles/team.css";
export const membercard = ({ user }) => {
const [src, name, email, designation] = this.user;
return (
<div className="col-lg-4">
<img src={src} className="image-fluid" alt="" />
<h5>{name}</h5>
<small>{designation}</small>
<div className="row">
<div id="email">
<span>
<i
className="fas fa-envelope-square"
style={{ fontSize: "20px", padding: "10px" }}
></i>
Email : {email}
</span>
</div>
</div>
</div>
);
};
and this is teamcard.js file
So say I want to display 3 members with their details .I want to reuse teamcard.js component,I need to know how I can pass the values as parameters from team.js to teamcard.js
THANK YOU IN ADVANCE
You can loop through a list of users and pass each one to the component. One note though, by spreading the user values with ...user your props are going to come in as ({ name, email, src, designation }). They way it's set up now you'll want to pass the full user as a prop.
export const Team = () => {
const users = [
{
name: "sowmi",
email: "sddsd",
src: { bs },
designation: "sgsfgsfg",
},
{
name: "user2",
email: "sassdf",
src: { sdfsdf },
designation: "vbnvbnvbn",
},
]
return (
<section className="team">
<div className="container text-center">
<h1 className="py-3">MEET OUR AMAZING TEAM</h1>
<p className="p1">
This the paragraph where you can write more details about your team
</p>
{users.map(u =>
<div className="row" key={u.email}>
<member user={u} />|
</div>
)}
</div>
</section>
);
};
I think you're using some of the class methods to access props when you're using Functional Components.
Click the "Run Code Snipper" button below to see it working.
// Notice the spread operators ({ }) here that are extracting the key from the object user passed
const MemberCard = ({ src, name, email, designation }) => {
// Alternatively you could do:
// MemberCard = (props) => {
// const { src } = props;
return (<div className="col-lg-4">
<img src={src} className="image-fluid" alt="" />
<h5>{name}</h5>
<small>{designation}</small>
<div className="row">
<div id="email">
<span>
<i
className="fas fa-envelope-square"
style={{ fontSize: "20px", padding: "10px" }}
></i>
Email : {email}
</span>
</div>
</div>
</div>)
};
const Team = () => {
// Notice the change too for the 'src' for the image as well
const user = {
name: "sowmi",
email: "sddsd",
src: 'https://loremflickr.com/320/240',
designation: "sgsfgsfg",
};
return (
<section className="team">
<div className="container text-center">
<h1 className="py-3">MEET OUR AMAZING TEAM</h1>
<p className="p1">
This the paragraph where you can write more details about your team
</p>
<div className="row">
<MemberCard {...user} />
</div>
</div>
</section>
);
};
ReactDOM.render(<Team />, document.querySelector('#root'));
<body>
<div id="root"></div>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="main.js"></script>
</body>

React Error: Cannot read property 'map' of undefined

I have been trying to resolve this error for almost 2 hours but no luck. I have even researched and used the bind method but still no luck with mapping a props that was passed through a parent component. Your help will be greatly appreciated.
import React from "react";
import { Link } from "react-router-dom";
const PostList = ({ postItem }) => {
postItem.map((post) => (
<div className="mx-auto mb-3 card w-75" key={post.id}>
<div className="card-body">
<h5 className="card-title">{post.title}</h5>
<p className="card-text">{post.comment}</p>
<Link to="/create">
<ion-icon
style={{ color: "#fc5185", fontSize: "20px" }}
name="trash-outline"
></ion-icon>
</Link>
</div>
</div>
));
};
export default PostList;
And the parent component is
class Dashboard extends Component {
state = {
posts: [
{
id: 1,
title: "Hello",
comment: "it is sunny today",
},
],
};
createPost = (title, comment) => {
const newPost = {
id: Math.floor(Math.random() * 1000),
title,
comment,
};
this.setState({
posts: [...this.state.posts, newPost],
});
};
render() {
return (
<div>
<CreatePost createPost={this.createPost} />
<PostList postItem={this.state.posts} />
</div>
);
}
}
export default Dashboard;
I guess you have missed to add return to the PostList component, you can do it in three ways (read about arrow functions)
const PostList = ({ postItem }) => postItem.map((post) => (
<div className="mx-auto mb-3 card w-75" key={post.id}>
<div className="card-body">
<h5 className="card-title">{post.title}</h5>
<p className="card-text">{post.comment}</p>
</div>
</div>
));
const PostList = ({ postItem }) => (
postItem.map((post) => (
<div className="mx-auto mb-3 card w-75" key={post.id}>
<div className="card-body">
<h5 className="card-title">{post.title}</h5>
<p className="card-text">{post.comment}</p>
</div>
</div>
));
);
const PostList = ({ postItem }) => {
return postItem.map((post) => (
<div className="mx-auto mb-3 card w-75" key={post.id}>
<div className="card-body">
<h5 className="card-title">{post.title}</h5>
<p className="card-text">{post.comment}</p>
</div>
</div>
));
}
Here is the working example

React app state not updating

I am creating a basic React app to hold books on certain shelves and am trying to create the functionality to move books between shelves.
The problem I have is that when I select the new target shelf from the book objects dropdown, the onUpdateShelf method in ListBooks.js does not seem to initiate the update and subsequent state change.
I am new to React, my understanding is that calling the setState function in updateShelf should trigger the re-render with the updated object.
My question then is, is my implementation wrong and where?
App.js
import React, { Component } from 'react'
import ListBooks from './ListBooks'
import * as BooksAPI from './utils/BooksAPI'
import { Route } from 'react-router-dom'
class BooksApp extends Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll()
.then((books) => {
this.setState(() => ({
books
}))
})
}
updateShelf = (book, shelf) => {
console.log(book)
console.log(shelf)
this.books.forEach(b => {
if(b.id === book.id && b.shelf !== book.shelf ) {
b.shelf = shelf
this.setState((currentState) => ({
books: currentState.books
}))
}
});
BooksAPI.update(book, shelf)
}
render() {
return (
<div>
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={this.updateShelf}
/>
)} />
</div>
)
}
}
export default BooksApp
And my ListBooks.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import './App.css'
const shelves = [
{
key: 'currentlyReading',
name: 'Currently Reading'
},
{
key: 'wantToRead',
name: 'Want To Read'
},
{
key: 'read',
name: 'Read'
}
];
class ListBooks extends Component {
static propTypes = {
books: PropTypes.array.isRequired
}
state = {
showSearchPage: false,
query: ''
}
render() {
const { books, onUpdateShelf } = this.props
function getBooksForShelf(shelfKey) {
return books.filter(book => book.shelf === shelfKey);
}
console.log(books);
return(
<div className="app">
{this.state.showSearchPage ? (
<div className="search-books">
<div className="search-books-bar">
<a className="close-search" onClick={() => this.setState({ showSearchPage: false })}>Close</a>
<div className="search-books-input-wrapper">
{/*
NOTES: The search from BooksAPI is limited to a particular set of search terms.
You can find these search terms here:
https://github.com/udacity/reactnd-project-myreads-starter/blob/master/SEARCH_TERMS.md
However, remember that the BooksAPI.search method DOES search by title or author. So, don't worry if
you don't find a specific author or title. Every search is limited by search terms.
*/}
<input type="text" placeholder="Search by title or author"/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid"></ol>
</div>
</div>
) : (
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<div className="list-books-content">
<div>
{ shelves.map((shelf) => (
<div key={shelf.key} className="bookshelf">
<h2 className="bookshelf-title">{shelf.name}</h2>
<div className="bookshelf-books">
<ol className="books-grid">
<li>
{ getBooksForShelf(shelf.key).map((book) => (
<div key={book.id} className="book">
<div className="book-top">
<div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
<div className="book-shelf-changer">
<select>
<option value="none" disabled>Move to...</option>
<option value="currentlyReading" onClick={() => onUpdateShelf(book, 'currentlyReading')} >Currently Reading</option>
<option value="wantToRead" onClick={() => onUpdateShelf(book, 'wantToRead')} >Want to Read</option>
<option value="read" onClick={() => onUpdateShelf(book, 'read')} >Read</option>
<option value="none" onClick={() => onUpdateShelf(book, '')} >None</option>
</select>
</div>
</div>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.author}</div>
</div>
))}
</li>
</ol>
</div>
</div>
)) }
</div>
</div>
<div className="open-search">
<a onClick={() => this.setState({ showSearchPage: true })}>Add a book</a>
</div>
</div>
)}
</div>
)
}
}
export default ListBooks
When you passe updateShelf to your component ListBooks, you lose the value of this inside updateShelf, and as a result this.books will be undefined.
You can solve this by, either doing this inside the constructor of BooksApp :
this.updateShelf = this.updateShelf.bind(this)
Or by using arrow functions:
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={() => { this.updateShelf()} }
/>
)} />
EDIT
You are already using arrow functions inside BooksApp, so what I said before isn't necessary.
But still, you should use this.state.books and not this.books inside updateShelf.

Resources