I have a problem, when I try to pass a .json like this:
this is my class
import MyForm from './MyForm';
class CreateProject extends React.Component{
constructor(){
super();
this.state = { categories:[]}
}
getCategories(){
API.get(`/categories/public`)
.then(resp=>{
this.setState({categories: resp.data})
})
.catch(err => {
console.log(err)
})
}
ComponentDidMOunt(){
// here show me the API correct like this
// 0:{id:1, name:"categorie one"}
// 1:{id:11, name:"categorie four"}
// 2:{id:19, name:"categorie five"}
// 3:{id:16, name:"categorie six"}
this.getCategories()
}
render(){
return(<div> <MyForm categories={this.state.categories}/></div>)
}
}
my functional component
export const MyForm = ({categories}) =>{
return(
<div>
<select >
{ // here not working because .map not belong a function categories
categories.map(category =>(
<option value={category.id}>{category.name}</option>
))
}
</select>
</div>)
}
how to read a categories inside my functional component using a loop . please something suggestion or a tip
thanks for your attention.
A couple things I noticed
componentDidMount() spelling error and an incorrect import. Should be:
import { MyForm } from './MyForm'
Here's a very similar working example. I'm just using a different api and I have an async function, also added some null checks on categories (might be redundant?).
https://codesandbox.io/s/modern-frog-0wmyu
import React from "react";
import { MyForm } from "./my-form";
class CreateProject extends React.Component {
constructor() {
super();
this.state = { categories: [] };
}
async getCategories() {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts`);
const data = await response.json();
this.setState({ categories: data });
}
componentDidMount() {
// here show me the API correct like this
// 0:{id:1, name:"categorie one"}
// 1:{id:11, name:"categorie four"}
// 2:{id:19, name:"categorie five"}
// 3:{id:16, name:"categorie six"}
this.getCategories();
}
render() {
const { categories } = this.state;
return (
<div>
{categories && categories.length > 0 && (
<MyForm categories={categories} />
)}
</div>
);
}
}
export default CreateProject;
MyForm component
import React from "react";
// I'm using the title property, but for your API it should be category.name
export const MyForm = ({ categories }) => (
<select>
{categories &&
categories.map(category => (
<option value={category.id}>{category.title}</option>
))}
</select>
);
Related
I have made for me a Tutorial-Project where I collect various React-Examples from easy to difficult. There is a "switch/case" conditional rendering in App.js, where I - depending on the ListBox ItemIndex - load and execute the selected Component.
I am trying to optimize my React code by removing the "switch/case" function and replacing it with a two dimensional array, where the 1st column contains the Component-Name 2nd column the Object. Further I would like to lazy-load the selected components.
Everything seems to work fine, I can also catch the mouse events and also the re-rendering begins but the screen becomes white... no component rendering.
App.js
import SampleList, { sampleArray } from './SampleList';
class App extends React.Component {
constructor(props) {
super(props);
this.selectedIndex = -1;
}
renderSample(index) {
if((index >= 0) && (index < sampleArray.length)) {
return React.createElement(sampleArray[index][1])
} else {
return <h3>Select a Sample</h3>;
}
}
render() {
return (
<header>
<h1>React Tutorial</h1>
<SampleList myClickEvent={ this.ClickEvent.bind(this) }/>
<p />
<div>
<Suspense> /**** HERE WAS MY ISSUE ****/
{ this.renderSample(this.selectedIndex) }
</Suspense>
</div>
</header>
);
}
ClickEvent(index) {
this.selectedIndex = index;
this.forceUpdate();
}
}
SampleList.js
import React from 'react';
const SimpleComponent = React.lazy(() => import('./lessons/SimpleComponent'));
const IntervalTimerFunction = React.lazy(() => import('./lessons/IntervalTimerFunction'));
const sampleArray = [
["Simple Component", SimpleComponent],
["Interval Timer Function", IntervalTimerFunction]
];
class SampleList extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.selectOptions = sampleArray.map((Sample, Index) =>
<option>{ Sample[0] }</option>
);
}
render() {
return (
<select ref={this.myRef} Size="8" onClick={this.selectEvent.bind(this)}>
{ this.selectOptions }
</select>
);
}
selectEvent() {
this.props.myClickEvent(this.myRef.current.selectedIndex);
}
}
export default SampleList;
export { sampleArray };
You have several issues in that code:
If you use React.lazy to import components dynamically, use Suspense to show a fallback;
The select can listen to the change event, and receive the value of the selected option, that is convenient to pass the index in your case;
Changing a ref with a new index doesn't trigger a re-render of your components tree, you need to perform a setState with the selected index;
I suggest you to switch to hooks, to have some code optimizations;
Code:
import React, { Suspense, useState, useMemo } from 'react';
const SimpleComponent = React.lazy(() => import('./lessons/SimpleComponent'));
const IntervalTimerFunction = React.lazy(() =>
import('./lessons/IntervalTimerFunction'));
const sampleArray = [
['Simple Component', SimpleComponent],
['Interval Timer Function', IntervalTimerFunction],
];
export default function App() {
const [idx, setIdx] = useState(0);
const SelectedSample = useMemo(() => sampleArray[idx][1], [idx]);
const handleSelect = (idx) => setIdx(idx);
return (
<Suspense fallback={() => <>Loading...</>}>
<SampleList handleSelect={handleSelect} />
<SelectedSample />
</Suspense>
);
}
class SampleList extends React.Component {
constructor(props) {
super(props);
}
selectEvent(e) {
this.props.handleSelect(e.target.value);
}
render() {
return (
<select ref={this.myRef} Size="8" onChange={this.selectEvent.bind(this)}>
{sampleArray.map((sample, idx) => (
<option value={idx}>{sample[0]}</option>
))}
</select>
);
}
}
Working example HERE
I'm having a hard time converting these 3 class components to function components, the class components are working i just am trying to convert them for learning purposes.
the API call: yelp.js
const { default: SearchBar } = require("../components/SearchBar/SearchBar");
const Yelp = {
searchYelp(term, location) {
return fetch(`/api/hello?term=${term}&location=${location}`)
.then((response) => {
// console.log(response)
return response.json()
}).then((jsonResponse) => {
// console.log(jsonResponse)
if (jsonResponse.businesses) {
return jsonResponse.businesses.map((business) => {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories.title,
rating: business.rating,
reviewCount: business.review_count,
}
})
}
})
}
}
export default Yelp
The Home component as a function that renders a SearchBar and BusinessList component: Home.js
import React, { useState } from "react";
import BusinessList from '../../../src/components/BusinessList/BusinessList';
import SearchBar from '../../../src/components/SearchBar/SearchBar';
import Yelp from '../../util/yelp';
const Home = (term, location) => {
const [businesses, setBusinesses] = useState([]);
const searchYelp = Yelp.searchYelp(term, location).then(businesses => {
setBusinesses(businesses)
})
return (
<>
<SearchBar searchYelp={searchYelp} />
<BusinessList business={businesses} />
</>
)
}
export default Home;
The Home component as a class: Home.js
// import React from 'react';
// import BusinessList from '../../../src/components/BusinessList/BusinessList';
// import SearchBar from '../../../src/components/SearchBar/SearchBar';
// import Yelp from '../../util/yelp';
// class Home extends React.Component {
// constructor() {
// super();
// this.state = {
// businesses: [],
// };
// this.searchYelp = this.searchYelp.bind(this);
// }
// searchYelp(term, location, sortBy) {
// Yelp.searchYelp(term, location, sortBy).then((businesses) => {
// this.setState({ businesses: businesses })
// })
// }
// render() {
// return (
// <>
// <SearchBar searchYelp={this.searchYelp} />
// <BusinessList businesses={this.state.businesses} />
// </>
// )
// }
// }
// export default Home;
The BusinessList component as a function that renders a Business component: BusinessList.js
import React, { useState } from "react";
import './BusinessList.css';
import Business from '../Business/Business';
function BusinessList(businesses) {
console.log(businesses)
return (
<div className="BusinessList">
{
businesses.map(business => {
<Business key={business.id} business={business} />
})
}
</div>
)
};
export default BusinessList;
The BusinessList component as a class: BusinessList.js
// import React from 'react';
// import './BusinessList.css';
// import Business from '../Business/Business';
// class BusinessList extends React.Component {
// constructor(props) {
// super(props)
// console.log(props.businesses)
// }
// render() {
// return (
// <div className="BusinessList">
// {
// this.props.businesses.map((business) => {
// return <Business key={business.id} business={business} />
// })
// }
// </div>
// )
// }
// };
// export default BusinessList;
The Business component as a function: Business.js
import React from "react";
import './Business.css';
const Business = (business) => {
return (
<div className="Business">
<div className="image-container">
<img src={business.business.imageSrc} alt={business.imageSrc} />
</div>
<h2>{business.business.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{business.business.address}</p>
<p>{business.business.city} {business.state} {business.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{business.business.category}</h3>
<h3 className="rating">{business.business.rating}</h3>
<p>{business.business.reviewCount} reviews</p>
</div>
</div>
</div>
)
};
export default Business;
The Business component as a class: Business.js
// import React from "react";
// import './Business.css';
// class Business extends React.Component {
// render() {
// const { business } = this.props
// return (
// <div className="Business">
// <div className="image-container">
// <img src={business.imageSrc} alt={business.imageSrc} />
// </div>
// <h2>{business.name}</h2>
// <div className="Business-information">
// <div className="Business-address">
// <p>{business.address}</p>
// <p>{business.city} {business.state} {business.zipCode}</p>
// </div>
// <div className="Business-reviews">
// <h3>{business.category}</h3>
// <h3 className="rating">{business.rating}</h3>
// <p>{business.reviewCount} reviews</p>
// </div>
// </div>
// </div>
// )
// }
// };
// export default Business;
EDIT **
My attempt at SearchBar component as function: SearchBar.js
import React, { useState, useEffect } from "react";
import './SearchBar.css';
const SearchBar = (props) => {
const [term, setTerm] = useState('')
const [location, setLocation] = useState('')
const [sortBy, setSortBy] = useState('best_match')
const sortByOptions = {
'Best Match': 'best_match',
'Highest Rated': 'rating',
'Most Reviewed': 'review_count'
};
const handleSortByChange = () => {
setSortBy(sortBy)
// console.log(sortByOption)
console.log(sortBy)
}
const renderSortByOptions = (sortByOptions) => {
// console.log(Object.keys(sortByOptions))
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption]
// console.log(sortByOptionValue)
return <li
className={sortBy === sortByOption ? 'active' : ''}
onClick={handleSortByChange}
key={sortByOptionValue}>
{sortByOption}
</li>;
})
}
const handleTermChange = (event) => {
setTerm(event.target.value)
}
const handleLocationChange = (event) => {
setLocation(event.target.value)
}
const handleSearch = (event) => {
event.preventDefault()
props.searchYelp(term, location)
}
return (
<div className="SearchBar">
{props.searchYelp}
<div className="SearchBar-sort-options">
<ul>
{renderSortByOptions(sortByOptions)}
</ul>
</div>
<div className="SearchBar-fields">
<input
onChange={handleTermChange}
placeholder="Search Businesses"
/>
<input
onChange={handleLocationChange}
placeholder="Where?"
/>
<button className="SearchBar-submit" onClick={handleSearch}>Let's Go</button>
</div>
</div>
)
}
export default SearchBar;
EDIT**
SearchBar component as a class: SearchBar.js
import React from 'react';
import './SearchBar.css';
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
term: '',
location: '',
sortBy: 'best_match'
}
this.handleTermChange = this.handleTermChange.bind(this)
this.handleLocationChange = this.handleLocationChange.bind(this)
this.handleSearch = this.handleSearch.bind(this)
this.sortByOptions = {
'Best Match': 'best_match',
'Highest Rated': 'rating',
'Most Reviewed': 'review_count'
};
}
getSortByClass(sortByOption) {
// console.log(sortByOption)
if (this.state.sortBy === sortByOption) {
return 'active'
}
return ''
}
handleSortByChange(sortByOption) {
this.setState({
sortBy: sortByOption
})
}
handleTermChange(event) {
this.setState({
term: event.target.value
})
}
handleLocationChange(event) {
this.setState({
location: event.target.value
})
}
handleSearch(event) {
this.props.searchYelp(this.state.term, this.state.location, this.state.sortBy)
event.preventDefault()
}
renderSortByOptions() {
return Object.keys(this.sortByOptions).map(sortByOption => {
let sortByOptionValue = this.sortByOptions[sortByOption]
console.log(sortByOptionValue)
return <li
onClick={this.handleSortByChange.bind(this, sortByOptionValue)}
className={this.getSortByClass(sortByOptionValue)}
key={sortByOptionValue}>
{sortByOption}
</li>;
})
}
render() {
return (
<div className="SearchBar">
{this.searchYelp}
<div className="SearchBar-sort-options">
<ul>
{this.renderSortByOptions()}
</ul>
</div>
<div className="SearchBar-fields">
<input onChange={this.handleTermChange} placeholder="Search Businesses" />
<input onChange={this.handleLocationChange} placeholder="Where?" />
<button className="SearchBar-submit" onClick={this.handleSearch}>Let's Go</button>
</div>
</div>
)
}
};
export default SearchBar;
I keep getting the error "Cannot read properties of undefined (reading 'map')
Or the error "Businesses.map is not a function"
Im also a little confused as to why when everything is converted to function components in my final Business component in order to get things to showup im required to pass things in as business.business.imageSrc instead of just business.imageSrc
First in Home searchYelp should be declared as a function so it can be passed as a callback to the SearchBar component.
const Home = () => {
const [businesses, setBusinesses] = useState([]);
const searchYelp = (term, location) => {
Yelp.searchYelp(term, location)
.then(businesses => {
setBusinesses(businesses);
});
};
return (
<>
<SearchBar searchYelp={searchYelp} />
<BusinessList business={businesses} />
</>
)
};
Then in BusinessList you need to access the passed business prop. Your current code is naming the props object businesses and then attempts to map it. It could be businesses.business.map, but by convention we name the props object props or simply destructure the props you want to use. You need to also return the Business component you are mapping to.
function BusinessList({ business }) {
return (
<div className="BusinessList">
{business.map(business => {
return <Business key={business.id} business={business} />;
})}
</div>
)
};
Same issue with the props object name in the Business component.
const Business = (props) => {
return (
<div className="Business">
<div className="image-container">
<img src={props.business.imageSrc} alt={props.business.imageSrc} />
</div>
<h2>{props.business.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{props.business.address}</p>
<p>{props.business.city} {props.business.state} {business.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{props.business.category}</h3>
<h3 className="rating">{props.business.rating}</h3>
<p>{props.business.reviewCount} reviews</p>
</div>
</div>
</div>
)
};
BusinessList receives props, an object containing the props passed in.
The function parameter would either need to destructure it:
function BusinessList({ businesses }) { ... }
Or reference it off the props object:
function BusinessList(props) {
console.log(props.businesses)
// ...
}
Few notes:
Right now Yelp.searchYelp returns Promise<any[] | undefined>, i.e undefined is a legitimate value that the consume may get. Up to you to decide if setBusinesses(businesses) when businesses is undefined is useful or not, but in that case, handle it. Otherwise default to an empty array, setBusinesses(businesses ?? []) or throw an error.
Do not run side effects in the render phase, i.e call the api inside a useEffect:
React.useEffect(() => {
const searchYelp = Yelp.searchYelp(term, location).then(businesses => {
setBusinesses(businesses ?? [])
})
}, [term, location])
Lastly, const Business = (business) => { here business is actually the props object. You can simply destructure it const Business = ({ business }) => { to get the value directly.
I have this parent App.jsx, with two components <Child1/> and <Child2/> imported.
export default function App() {
const [isFlipped, setIsFlipped] = React.useState(false);
const handleSelectPlayers = () => {
setIsFlipped(true);
}
const handleDeselectPlayers = () => {
setIsFlipped(false);
}
return (
<Flippy
isFlipped={isFlipped}
flipDirection="horizontal" // horizontal or vertical
style={{ width: "400px", height: "600px" }} /// these are optional style, it is not necessary
>
<FrontSide>
<Child1 onSelectPlayers={handleSelectPlayers} /> // <-----
</FrontSide>
<BackSide>
<Child2 onDeselectPlayers={handleDeselectPlayers} /> // <-----
</BackSide>
</Flippy>
);
}
This is Child1.jsx, where I have 'players' set locally by this.setState():
class Child1 extends Component {
constructor(props) {
super(props);
this.state = {
players:[]
};
}
async getPlayers() {
const res = await fetch("/json/players.json");
const data = await res.json();
const players = Object.values(data.Players)
this.setState({
players: players
},() => console.log(this.state.players));
}
handlePlayers = () => {
this.props.onSelectPlayers();
};
render() {
return (
...
<Button handleClick={() => this.handlePlayers()}></Button>
...
);
And here Child2.jsx, which needs 'players' as props, given the fact they are fetched at Child1.jsx.
class Child2 extends Component {
constructor(props) {
super(props);
this.state = {
players:[]
};
}
handlePlayers = () => {
// do something with players here
};
handleChangePlayers = () => {
this.props.onDeselectPlayers();
};
render() {
return (
...
<Button handleClick={() => this.handlePlayers()}>
<Button handleClick={() => this.handleChangePlayers()}>
...
);
}
I know I can achieve this by having a callback to App.jsx at Child1.jsx, so I can pass players as props to Child2.jsx, but how so?
You can keep the players state on the Parent of both Child components. This way, you can pass it down as props to the relevant components. Refer to my comments on the code for insight
function App(){
const [players, setPlayers] = React.useState(); // single source of truth for players
return (
<React.Fragment>
<Child1 setPlayers={setPlayers}/> // pass state setter to Child1 where you perform the xhr to fetch players
<Child2 players={players}/> // pass players down as props to Child2
</React.Fragment>
)
}
class Child1 extends React.Component{
componentDidMount(){
this.getPlayers(); // sample fetching of players
}
getPlayers() {
this.props.setPlayers([ // set players state which resides on the parent component "App"
"foo",
"bar"
]);
}
render() {return "Child1"}
}
class Child2 extends React.Component{
componentDidUpdate(){
// this.props.players contains updated players
console.log(`Child2 players`, this.props.players);
}
render() {return "Child2"}
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have a list of ids (integer) and I have multiple components.
After a request to my API, the component receives a list of ids that should already be active.
I want to simulate a click on each element with the same id as the one in my array. I know I can use refs to do that, but I don't undertstand how to make it works with a list of elements.
Here's my code :
import React, { Component } from 'react'
import InterestBox from './InterestBox'
import Axios from 'axios'
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList
import React, { Component } from 'react'
export class InterestBox extends Component {
constructor(props) {
super(props);
this.images = require('../../img/interests/*.svg');
this.state = {activated: false};
this.interest_box_content = React.createRef();
this.interest_text = React.createRef();
this.handleClick = this.handleClick.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleClick() {
this.props.handleClick(this.props.id, this.props.title);
this.setState(prevState => ({
activated: !prevState.activated
}))
}
updateDimensions() {
console.log((window.getComputedStyle(this.refs.interest_box_content).width))
this.refs.interest_text = (window.getComputedStyle(this.refs.interest_box_content).width)
}
render() {
return (
<div className="column is-one-fifth-desktop is-half-touch">
<div className="interest-box">
<div className="interest-box-adjuster">
<div ref={"interest_box_content"} className={"interest-box-content " + (this.state.activated == true ? 'interest-box-activated' : '')} onClick={this.handleClick}>
<img className="interest-icon" src={this.images[this.props.icon]} style={{'height': '50%'}}></img>
<i className="activated-icon fas fa-check"></i>
<span ref={"interest_text"} className="interest-text">{this.props.title}</span>
</div>
</div>
</div>
</div>
)
}
}
export default InterestBox
In the InterestList "componentDidUpdate" method, the value of the item is an integer.
I want to use this integer to "click" on the InterestBox with the corresponding "id".
How can I achieve this ?
You can store an array of elements in one ref, like this:
constructor(props) {
super(props);
this.state = {pinterests: []}
this.pinterestRefs = React.createRef()
}
...
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} ref={pinterestRef => this.refs.pinterestRefs.push(pinterestRef)} />
})}
</React.Fragment>
)
}
and then call the click function on each in a componentDidMount function:
componentDidMount() {
if (this.refs.pinterestRefs.length) {
this.refs.pinterestRefs.forEach(pinterestEl => {
pinterestEl.click();
});
}
}
Since this.pinterestRefs is a ref and not an array, the push method is not available. Unfortunately, we do not have a definite length so we can't declare the refs preemptively. However, we can add it to this.refs object and the convert it to an array:
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(Object.values(this.refs)); // Array with all refs
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
{/*I'm assuming each item has a unique id, if not, create one*/}
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} ref={pinterest.id} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList;
I'm trying to get a feel for reactjs, new to front end development. Google Books API is simple so I decided to use it to build a react page that lists 10 books given the user input.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
const GOOGLE_BOOKS_API = 'https://www.googleapis.com/books/v1/volumes?q=';
const GOOGLE_BOOKS_API_LIMIT = 'maxResults=10';
class BookItem extends React.Component {
render() {
return (
<div>
<img alt="Book" src={this.props.data.volumeInfo.imageLinks.thumbnail} />
<span>{this.props.data.volumeInfo.imageLinks.thumbnail}</span>
</div>
);
}
}
class BookResults extends React.Component {
render() {
const isBookResultsEmpty = !(this.props.books && this.props.books.length > 1);
const bookItems = isBookResultsEmpty ? [] : this.props.books.map((book,index) =>
<BookItem key={index} data={book} />
);
return (
<div className='book-results'>
{isBookResultsEmpty ? (
<h1>No Results</h1>
) : (
<div> {bookItems} </div>
)}
</div>
);
}
}
class BookSearch extends React.Component {
constructor(props) {
super(props);
this.state = {
bookQuery: '',
books: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.getBooks = this.getBooks.bind(this)
}
getBooks() {
let queryString = '';
if (this.state.bookQuery && this.state.bookQuery.length > 1) {
queryString = this.state.bookQuery.replace(/\s/g, '+');
fetch(`${GOOGLE_BOOKS_API}${queryString}&${GOOGLE_BOOKS_API_LIMIT}`)
.then(results => {
return results.json();
})
.then(json => {
this.setState({
books: json.items
});
})
.catch(e => console.log('error', e));
}
}
handleInputChange(event) {
this.setState({
bookQuery: this.search.value
},
this.getBooks());
}
render() {
return (
<div className="book-search">
<form>
<input
placeholder="Search for Books"
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
</form>
<BookResults books={this.state.books} />
</div>
);
}
}
ReactDOM.render(<BookSearch />, document.getElementById('root'));
I get an error when typing the input:
TypeError: Cannot read property 'thumbnail' of undefined
I added a check for the data in the BookResults component but the error still occurs. I assume it has to do with the state or props value changing while rendering, but I don't know enough about React to be sure
Some books don't have imageLinks, so you need to make sure it is not undefined before you use it.
Example
class BookItem extends React.Component {
render() {
const { imageLinks } = this.props.data.volumeInfo;
if (!imageLinks) {
return null;
}
return (
<div>
<img alt="Book" src={imageLinks.thumbnail} />
<span>{imageLinks.thumbnail}</span>
</div>
);
}
}