React - Cannot render list item that is retrieved from API - reactjs

I am writing a react application that outputs a list of books (image, title, category, and description).
My search bar and booklist are sibling components and the search bar will pass data to the booklist.
when clicking the search button, only "Sample Category" shows up but not anything else. There is no problem accessing the API and the data is not null.
Here is a sample API output: https://www.googleapis.com/books/v1/volumes?q=lordoftherings
My code is the following:
// App
import React, { Component } from 'react';
import Axios from 'axios';
import './App.css';
import SearchBar from './SearchBar';
import BookList from './BookList';
class App extends Component {
constructor(props) {
super(props);
this.state = {
books: []
};
this.search = this.search.bind(this);
}
search(title) {
const promise = Axios.get('https://www.googleapis.com/books/v1/volumes?q=' + title);
promise.then((response) => {
const books = response.data.items;
this.setState({ books: books });
console.log(this.state.books);
})
};
render() {
return (
<div className="App">
<SearchBar searchBooks = {this.search}/>
<BookList booklist = {this.state.books}/>
</div>
);
}
}
export default App;
// Search Bar
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { titleToSearch: 'harry potter' }
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
this.setState({ titleToSearch: e.target.value });
};
render() {
return (
<form>
<input
type="text"
name="booksInput"
placeholder="Enter book title"
value={this.state.titleToSearch}
onChange={this.handleInputChange}
/>
<button type="button" onClick={() => this.props.searchBooks(this.state.titleToSearch)}>Search</button>
</form>
);
}
}
export default SearchBar;
// BookList
import React, { Component } from 'react';
class BookList extends Component {
render() {
const books = this.props.booklist;
return (
<div className="table">
{books.map((book) => {
console.log(book.id);
return (
<div className="box" key={book.id}>
<div className="img"><img src="assets/default-placeholder.jpg" alt="" /></div>
<div className="title">{book.title}</div>
<div className="category">Sample Category</div>
<div className="description">{book.description}</div>
</div>
);
})}
</div>
);
}
}
export default BookList;

In the sample code you provided, you're not actually dynamically outputting categories.
You've hard coded 'Sample category' in there.
book.category
...is not actually in the dataset.
There are categories which seem to be available under:
<div className="category">{book.volumeInfo.categories[0]}</div>
although you'll want to check if the array has length, and probably map or join each item in array to string.
just to be clear: the issue with your other fields is also that they're children of "volumeInfo"

Related

REACT - Why isnt my Filter Method Working

Im trying to simply create a search and results feature for an app. The value of the input should reflect the components listed in the CardList Array. The filter doesn't seem to update the CardList. I've logged steps along the way and I've come to the conclusion that its the filter I set up. I cant seem to figure out why it wont filter the list.
import React, {Component} from 'react';
import CardList from './CardList';
import {robots} from './robots';
import './index.css';
class App extends Component {
constructor() {
super()
this.state = {
robots: robots,
searchfield: ''
}
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
}
render() {
const filteredRobots = this.state.robots.filter(robot => {
return robot.name.toLowerCase().includes(this.state.searchfield.toLowerCase());
});
return (
<div className="appAlign">
<h1 className="appTitle">RoboFriends</h1>
<input
className="searchBox"
type="search"
placeholder="Search Robots"
onChange={this.onSearchChange}
/>
<CardList robots={filteredRobots} />
</div>
);
}
}
export default App;
The error is not caused by the filter function as I have tested it and it works. It most probably lies with the robots data-set. I have slightly modified the filter function here.
import React, { Component } from "react";
import CardList from "./CardList";
import { robots } from "./robots";
class App extends Component {
constructor() {
super();
this.state = {
robots: robots,
searchfield: ""
};
}
onSearchChange = event => {
this.setState({ searchfield: event.target.value });
};
render() {
const filteredRobots = this.state.robots.filter(robot =>
robot.name.toLowerCase().includes(this.state.searchfield.toLowerCase())
);
return (
<div className="appAlign">
<h1 className="appTitle">RoboFriends</h1>
<input
className="searchBox"
type="search"
placeholder="Search Robots"
onChange={this.onSearchChange}
/>
<CardList robots={filteredRobots} />
</div>
);
}
}
export default App;
I have made a sandbox with your code which has a sample robots data and a Card that renders the filtered data-set. Take a look.

react basic todo list with edits and storing state

I need some help building a todo list with React. Im a beginner so there are a lot of aspects of react I don't understand. I've created a Todo list at the moment this lists an un ordered list with the input, edit and remove button a select menu and a further input field.
Firstly Im confused to where I update my state. the "App.js" is where my main state is stored and im trying to keep it that way. I need to know how to edit the input field (todo listitem) which stores the new value. Im then looking to create a "completed list" where i want to store the input field as well as the select option (which ever is clicked) Please could someone give me some guidelines for this. Thank you in advance
import React, { Component } from 'react';
import Form from './Components/Form'
import './App.css';
import List from './Components/List'
import Completed from './Components/Completed'
class App extends Component {
constructor(props){
super(props)
this.state={
isEditing:false,
text:"",
items:[],
completed:[
{
}
]
}
this.submit=this.submit.bind(this);
this.eventHandler=this.eventHandler.bind(this)
}
submit=(e)=>{
e.preventDefault();
this.setState({
items:[
{
name:this.state.text,
},
...this.state.items
],
text:""
})
}
remove=(index)=>{
this.setState({
items:this.state.items.filter((_,i) => i!==index)
})
}
onChange=(index)=>{
this.setState({
items:this.state.items.filter((_,i) => i!==index)
});
}
eventHandler=(e)=>{
this.setState ({
text:e.target.value
})
}
handleNameEdits=()=>{
this.setState({
isEditing:true
})
}
edit=()=>{
this.setState({
isEditing:!this.state.isEditing
})
}
myoptions=(e)=>{
this.setState({
completed:[
{
webmaster:e
},
...this.state.completed
]
})
}
render() {
return (
<div className="App">
<header className="App-header">
<Form submit={this.submit} myValue={this.state.text} eventHandler=
{this.eventHandler}/>
{this.state.items.map && this.state.items.map((item,index)=>(
<List key={index}
name={item.name}
edit={this.edit}
change={()=>this.onChange(index)}
remove={()=>this.remove(index) }
handleNameEdits={this.handleNameEdits}
myoptions={(e =>this.myoptions(e.target.value))}
/>
))}
</header>
<div>
completed
</div>
</div>
);
}
}
export default App;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Edit from './Edit';
class List extends Component {
constructor(props) {
super()
this.options = [
{name:'web1'},
{name:'web2'},
{name:'web3'},
{name:'web4'}
];
}
render() {
const {key} = this.props;
const x=this.options;
return (
<ul>
<li>{this.props.name}
<button onClick={this.props.edit}>Edit</button>
<button onClick={this.props.remove}>Remove</button>
<select onChange={this.props.myoptions}>
{this.options.map(options => <option>{options.name}</option> )}
</select>
<label> Completed
</label><input type="checkbox" onChange=
{this.props.change} checked={this.props.change} onClick=
{this.props.submit}/>
<label> Ticket Number </label><input type='text'/>
</li>
</ul>
)
}
}
export default List;
import React from 'react'
import PropTypes from 'prop-types';
const Form= props=> {
return (
<form onSubmit={props.submit}>
<input type='text' value={props.myValue} onChange=
{props.eventHandler}/>
<button> click</button>
</form>
)
}
Form.PropTypes={
onSubmit:PropTypes.func.isRequired,
evenHandler:PropTypes.func.isRequired,
myValue:PropTypes.string.isRequired
}
export default Form

Passing method child's component to another 'external' component in ReactJS

I'm new to ReactJs, coding and this is my first time posting here! So, I'm trying to build a Todo app in ReactJs. I have four components.
the first compo. is App.js - the parent one
import React, { Component } from 'react';
import TaskTodo from './TaskTodo';
import './App.css';
import TaskDisplayed from "./TaskDisplayed";
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Hey, i'm the header! </h1>
</header>
<div className="App-intro">
<TaskTodo/>
</div>
<div className="App-right">
<TaskDisplayed/>
</div>
</div>
);
}
}
export default App;
TaskTodo.js - which is the parent of the TodoItems.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
export default class TaskTodo extends Component{
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItem = this.addItem.bind(this);
};
addItem(e) {
const itemArray = this.state.items;
if (this._inputElement.value !== "") {
itemArray.unshift(
{
text: this._inputElement.value,
key: Date.now()
}
);
this.setState({
items: itemArray
});
this._inputElement.value = "";
}
e.preventDefault();
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit={this.addItem}>
<input type="text" ref={(a) => this._inputElement = a}
placeholder="Add a list">
</input>
</form>
</div>
<TodoItems entries={this.state.items}/>
</div>
);
}
}
TodoItems.js - the child of the TaskTodo.js
import React, { Component } from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.createTasks = this.createTasks.bind(this);
}
handleClick = (text) => {
console.log(text);
}
createTasks(item) {
return <li key={item.key}><a onClick={() => this.handleClick(item.key, item.text)} href={'#about'}>#{item.text}</a></li>
}
render() {
const todoEntries = this.props.entries;
const listItems = todoEntries.map(this.createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
};
export default TodoItems;
What I need to do, is how I can pass the handleClick method (a child's of TaskTodo) to an 'external' component - TaskDisplayed.js; or how I can track when the user click to a listed item? Please pardon me for this unprofessional way of asking! But, I truly need to get in track with ReactJS! Thanks!
p.s. The above code I found online, so thanks for that :D!
You should define the onClick event handler in the parent component and pass it to the child as a prop.
See How to pass an event handler to a child component in React
In this case, you would want to define it in the App component since that is the parent of the two components that need to communicate.

React Server Rendering : Warning: setState(...): Can only update a mounting component

I'm learning react + firebase. I also read about React server rendering & I want to test & see what is look like to have server rendering my app. so I use ReactDOMServer to render my component and can render my very simple component but I gotta problem when I try a component that uses rebase package for sync data between my app and Firebase. this is what warning I got:
Warning: setState(...): Can only update a mounting component. This
usually means you called setState() outside componentWillMount() on
the server. This is a no-op. Please check the code for the App
component.
here is my code:
App Component:
import React from 'react';
import Header from './Header';
import Orders from './Orders';
import Inventory from './Inventory';
import SampleFishes from '../sample-fishes';
import Fish from './Fish';
import base from '../base';
class App extends React.Component{
constructor(){
super();
this.addFish = this.addFish.bind(this);
this.loadSampleFishes = this.loadSampleFishes.bind(this);
this.addToOrder = this.addToOrder.bind(this);
this.liveUpdateFish = this.liveUpdateFish.bind(this);
this.removeFish = this.removeFish.bind(this);
this.removeOrder = this.removeOrder.bind(this);
this.state={
fishes:{},
orders:{}
};
}
componentWillMount(){
let prevOrder = localStorage.getItem(`${this.props.params.store}-order`);
this.ref = base.syncState(`${this.props.params.store}/fishes`,{
context:this,
state:`fishes`,
then(){
if(prevOrder){
this.setState({orders: JSON.parse(prevOrder)});
}
}
});
}
componentWillUnmount(){
base.removeBinding(this.ref);
}
componentWillUpdate(nextProps,nextState){
localStorage.setItem(`${this.props.params.store}-order`,JSON.stringify(nextState.orders));
}
loadSampleFishes(){
let fishes = {...this.state.fishes,...SampleFishes};
this.setState({fishes});
}
addFish(fish){
let fishes = {...this.state.fishes};
fishes[`fish-${Date.now()}`]= fish;
this.setState({fishes});
}
addToOrder(key){
let orders = {...this.state.orders};
orders[key] = orders[key]+1 ||1;
this.setState({orders});
}
liveUpdateFish(fish,k){
let fishes = {...this.state.fishes};
fishes[k] = fish;
this.setState({fishes});
}
removeFish(key){
let fishes = {...this.state.fishes};
fishes[key] = null;
this.setState({fishes});
this.removeOrder(key);
}
removeOrder(key){
let orders = {...this.state.orders};
delete orders[key];
this.setState({orders});
}
render(){
return (
<div className="catch-of-the-day">
<div className="menu">
<Header tagline="Fresh SeaFood Market"/>
<ul>
{
Object.keys(this.state.fishes).map(key=><Fish addToOrder={this.addToOrder} id={key} key={key} fish={this.state.fishes[key]} />)
}
</ul>
</div>
<Orders orders={this.state.orders} fishes={this.state.fishes} removeOrder={this.removeOrder}/>
<Inventory addFish={this.addFish} loadSampleFishes={this.loadSampleFishes} fishes={this.state.fishes} liveUpdate={this.liveUpdateFish} removeFish={this.removeFish} />
</div>
);
}
}
export default App;
Orders Component:
import React from 'react';
import Order from './Order';
import {formatPrice} from '../helpers';
class Orders extends React.Component{
render(){
const orderIDs = Object.keys(this.props.orders);
let list = null;
if(orderIDs.length===0){
list = <li>OOPS there is no item</li>;
}else{
list = Object.keys(this.props.orders).map(key=><Order removeOrder={this.props.removeOrder} fish={this.props.fishes[key]} key={key} index={key} order={this.props.orders[key]}/>)
}
const total = orderIDs.reduce((prev,index)=>{
if(!this.props.fishes[index]) return prev;
if(!this.props.fishes[index].price) return prev;
return prev+this.props.fishes[index].price*this.props.orders[index]},0);
return (
<div className="order-wrap">
<h2>Your Orders</h2>
<ul className="order">
{list}
<li className="total">total is {formatPrice(total)}</li>
</ul>
</div>
);
}
}
export default Orders;
Order Component:
import React from 'react';
import {formatPrice} from '../helpers';
class Order extends React.Component{
render(){
const removeButton = <button onClick={e=>this.props.removeOrder(this.props.index)}>❌</button>
return (
<li><span>{this.props.order}x {this.props.fish.name}</span><span className="price">{formatPrice(this.props.fish.price)}</span>{removeButton}</li>
);
}
}
export default Order;
Inventory Component:
import React from 'react';
import AddFishForm from './AddFishForm'
class Inventory extends React.Component{
constructor(props){
super(props);
this.returnInventory = this.returnInventory.bind(this);
this.handleChanges = this.handleChanges.bind(this);
}
handleChanges(e,key){
const fish = this.props.fishes[key];
const updatedFish = {...fish,
[e.target.name] : e.target.value
};
this.props.liveUpdate(updatedFish,key);
}
returnInventory(key){
const fish = this.props.fishes[key];
return(
<div className="fish-edit" key={key}>
<input name="name" value={fish['name']} type="text" placeholder="fish name" onChange={e=>this.handleChanges(e,key)} />
<input name="price" value={fish['price']} type="text" placeholder="fish price" onChange={e=>this.handleChanges(e,key)} />
<select name="status" value={fish['status']} onChange={e=>this.handleChanges(e,key)}>
<option value="available">Fresh!</option>
<option value="unavailable">Sold Out!</option>
</select>
<textarea name="desc" value={fish['desc']} type="text" placeholder="fish desc" onChange={e=>this.handleChanges(e,key)}></textarea>
<input name="image" value={fish['image']} type="text" placeholder="fish image" onChange={e=>this.handleChanges(e,key)} />
<button onClick={e=>this.props.removeFish(key)}>- Remove Fish</button>
</div>
)
}
render(){
return (
<div>
<h2>Inventory</h2>
{
Object.keys(this.props.fishes).map(key=>this.returnInventory(key))
}
<AddFishForm addFish={this.props.addFish}/>
<button onClick={this.props.loadSampleFishes}>Load Sample Fishes</button>
</div>
);
}
}
export default Inventory;
Fish Component:
import React from 'react';
import {formatPrice} from '../helpers';
class Fish extends React.Component{
addOrder(k){
console.log(k);
this.props.addToOrder(k);
}
render(){
let available = this.props.fish.status ==='available';
let buttonText = available?'Order it':'Sold Out';
return (<li className="menu-fish" id={this.props.id}>
<img src={this.props.fish.image} alt={this.props.fish.name}/>
<h3 className="fish-name">{this.props.fish.name}<span className="price">{formatPrice(this.props.fish.price)}</span>
</h3>
<p className="fish-desc">{this.props.fish.desc}</p>
<button disabled={!available} onClick={(e)=>this.addOrder(this.props.id)}>{buttonText}</button>
</li>);
}
}
export default Fish;

Passing state to more than one child component in React

I'm having trouble understanding how to pass state as props to other child components in React. In my code, you can see I've got a component that takes input and maps it to my state array, displaying part of that data in another component, that's working just fine.
But the overall goal is that when a user clicks on an item they've added to the list, React Router kicks in and changes the view to the MovieDetails component, which will have extra information they've entered, like title, date and description.
I haven't even gotten to setting up react router because I can't seem to properly access state within the MovieDetails component. And then I'm not quite sure how to display the correct MovieDetails component with router.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import './App.css';
class App extends Component {
constructor() {
super();
this.addMovie = this.addMovie.bind(this);
this.state = {
movies : []
};
}
addMovie(movie) {
let movies = this.state.movies;
movies.push(movie);
this.setState({ movies });
}
render() {
return (
<div className="wrapper">
<div className="container">
<div>
<h3 className="heading">Favorite Movies</h3>
</div>
</div>
<div>
<AddMovie addMovie={ this.addMovie }/>
<MovieList movies={ this.state.movies }/>
</div>
</div>
)
}
}
class AddMovie extends Component {
addMovie(event) {
event.preventDefault();
const movie = {
title : this.title.value,
year : this.year.value,
image : this.image.value,
desc : this.desc.value
}
this.props.addMovie(movie);
this.movieForm.reset();
}
render() {
return (
<div className="container">
<form ref={(input) => this.movieForm = input} onSubmit={(e) => this.addMovie(e)}>
<input ref={(input) => this.title = input} className="Input" type="text" placeholder="Title"/>
<input ref={(input) => this.year = input} className="Input" type="text" placeholder="Year"/>
<textarea ref={(input) => this.desc = input} className="Input" type="text" placeholder="Description"></textarea>
<input ref={(input) => this.image = input} className="Input" type="text" placeholder="Poster URL"/>
<button type="submit">Add</button>
</form>
</div>
)
}
}
class MovieList extends Component {
render() {
return (
<div>
{ this.props.movies.map( (movie, i) => <MovieListItem key={i} details={ movie }/> )}
</div>
);
}
}
class MovieListItem extends Component {
constructor(props) {
super(props);
this.toggleClass = this.toggleClass.bind(this);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
const { details } = this.props;
return (
<div
className={this.state.active ? "red": null}
onClick={this.toggleClass}
>
<img src={details.image} alt=""/>
<hr/>
</div>
)
}
}
class MovieDetails extends Component {
render() {
return (
<div>
<p>title here</p>
<p>year here</p>
<p>description here</p>
<img src="image" alt=""/>
</div>
)
}
}
export default App;
The problem come from the way you try to access the input values. When you use ref, you get a React wrapper, not the real DOM element, so you can't access directly to .value or .reset(). You have to use the getDOMNode() method to get the DOM element. This worked for me :
const movie = {
title : this.title.getDOMNode().value,
year : this.year.getDOMNode().value,
image : this.image.getDOMNode().value,
desc : this.desc.getDOMNode().value
};
...
this.movieForm.getDOMNode().reset();
An other thing, when you setState something that uses the current state, you should use the callback instead :
addMovie(newMovie) {
this.setState(({movies: prevMovies})=> ({
movies: [...prevMovies, newMovie]
}));
}
See complete setState API from official doc
If I got it right, do you want to push to a new component (where the details should be accessible) when you're clicking on an item created from MovieList? If so, here are the steps you have to do:
If you want to push a new view you have to use something like browserHistory or hashHistory from 'react-router'. In this case I'll use browserHistory.
To access the state in MovieDetails component simply pass it through browserHistory.
Here is the way I used your code to push to a new view when an item from MovieList component is clicked:
import {Router, Route, browserHistory} from "react-router";
class Routes extends Component {
render() {
let props = this.props;
return (
<Router history={browserHistory}>
<Route path="/" component={App}/>
<Route path="/movie-details" component={MovieDetails}/>
</Router>
)
}
}
// Here is your App component
class App extends Component {
// ... your code
}
// ... your other classes
class MovieListItem extends Component {
// ... Constructor
// Here I'm pushing the new route for MovieDetails view
toggleClass(details) {
browserHistory.push({
pathname: '/movie-details',
state: details // pass the state to MovieDetails
});
// ... your code
}
render() {
const {details} = this.props;
return (
<div
// ... your code
onClick={this.toggleClass.bind(this, details)} // pass details to toggleClass()
>
// ... your code
</div>
)
}
}
// Here is your Movie Details component
class MovieDetails extends Component {
console.log('This props: ', this.props.location.state); // The details object should be logged here
// ... your code
}
// Export Routes instead of App
export default Routes;
Hope that helps!

Resources