Image URL not accessible in an API Call - javascript

I'm trying to access an image URL in an API call in my react project but, the error message I'm getting is that the URL not defined as an error. Have tried to find the error but couldn't find it. Kindly assist in helping me to know what I'm doing wrong.
Below is my code. The place giving me a problem is commented out.
import React, { Component } from 'react'
import axios from "axios";
import { Card} from "react-bootstrap";
export default class App extends Component {
state = {
data: []
};
componentDidMount() {
this.fetchCountryData();
}
fetchCountryData = async () => {
const url = "https://api.thecatapi.com/v1/breeds";
try {
const response = await axios.get(url);
const data = await response.data;
this.setState({
data
});
} catch (error) {
console.log(error);
}
};
render() {
return (
<div className="App">
<h1>React Component Life Cycle</h1>
<h1>Calling API</h1>
<div>
{
this.state.data.map((item, id) => (
<div key={id}>
{console.log(item)}
<Card style={{ width: '18rem' }}>
{/* <Card.Img variant="top" src={item.image.url} /> This part is giving me the problem */}
<Card.Body>
<Card.Title>{item.name}</Card.Title>
<Card.Text>
{item.origin}
</Card.Text>
<Card.Text>
{item.temperament}
</Card.Text>
<Card.Text>
{item.weight.metric}
</Card.Text>
<Card.Text>
{item.description}
</Card.Text>
{/* <Button variant="primary">Go somewhere</Button> */}
</Card.Body>
</Card>
{/* { <img src={item.image.url} alt=""/> } Please why is this not working ? */}
{/* {item.name} <br/>
{item.origin} <br/>
{item.temperament} <br/>
{item.life_span} <br/>
{item.weight.metric} <br/>
{item.description} <br/> */}
</div>
))
}
</div>
</div>
)
}
}

This is interesting. My first thought was that when the component renders, item.image object does not exist so that's why you cannot access a property in an undefined object. But then I realized it doesn't throw for item.weight.metric. I will be waiting to see what other people have to say about this.
However, the basic fix is you can simply use image link as item.image && item.image.url or shortly item.image?.url so it doesn't throw.

Related

Create custom TabPane using react-bootstrap

I would create custom Tab Form from JSON, using React and boostrap. Using this function my code work correctly.
function DisplayForm(props) {
return(
<div className="row">
<Tabs className="mb-3">
{Array.from(new Map(Object.entries(json))).map((data) => (
<Tab eventKey={data[0]} title={data[0]}> </Tab>
))}
</Tabs>
</div>
);
}
}
data[0] is the tabName.
Now, I would create custom tab, based on json value. For example, something, like this:
function DisplayCustomTab(props) {
return(
<div className="row">
<TabContainer className="mb-3">
<TabContent>
{Array.from(new Map(Object.entries(json))).map((data) => (
<>
{(() => {
if(myCondition ) {
return(
<>
<CreateTAB data={data[0]} />
</>
)
}
})()}
</>
))}
</TabContent>
</TabContainer>
</div>
);
}
function CreateTAB(props) {
console.log("tab name: " + props.data); // this line works
return(
<TabPane eventKey={props.data} title={props.data}> </TabPane>
);
}
I using TabPane, TabContent and TabContainer because using Tab and Tabs I get following error:
component is not meant to be rendered! It's an abstract component that is only valid as a direct Child of the `Tabs` Component. For custom tabs components use TabPane and TabsContainer directly
Anyone can explain where I'm wrong? Thank you

React: object throws ".keys is not a function Error"

I'm aware that this error may be quite common and have been answered several times before, but I couldn't find a solution.
My code always throws this error: ".map is not a function". I know that this happens because data is not an array. So I tried to solve this with .keys function but this throws the ".keys is not a function" error. I'm declaring const data in the parent component and want to use it in the child component.
I think my error depends on a false use of .keys. But after much Googling I'm still not one step further.
This is my Child-Code:
import React from "react";
import Card from 'react-bootstrap/Card';
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import Container from 'react-bootstrap/Container';
import {Link} from 'react-router-dom'
const PostsRow = (data) => {
{return (
<Container>
<Row>
{data.keys(data).map((data) => {
console.log(data + "is mount")
return (
<Col className="col-6 col-md-6 col-lg-3 card">
<Link to={data.url}>
<Card className=" text-center ">
<Card.Img variant="top" src={data.imagesrc} />
<Card.Body>
<Card.Title>{data.title}</Card.Title>
</Card.Body>
</Card>
</Link>
</Col>
);
})}
</Row>
</Container>
);
};
export default PostsRow;
This is home.jsx (parent):
import React from "react";
import './route.css';
import PostsRow from "../components/Content/PostsRow";
const Home = () => {
const data = {
title: "Ersti",
imagesrc: "./490.jpg",
url: "/meineposts"
};
return (
<div>
<PostsRow data={data}/>
</div>
);
};
export default Home;
This is working fine as long as const data is declared in the PostsRow.jsx, but when I try to declare it in Home.jsx and use props the above error throws.
As data is an object. To get its keys, you should write: Object.keys(data).
And, you have a typo in props destructuring : it should be ({data}).
Your example data is simply an object, not an array, so you don't need to use map or Object.keys here, you can simply write:
<Col className="col-6 col-md-6 col-lg-3 card">
<Link to={data.url}>
<Card className="text-center">
<Card.Img variant="top" src={data.imagesrc} />
<Card.Body>
<Card.Title>{data.title}</Card.Title>
</Card.Body>
</Card>
</Link>
</Col>
PostsRow will be called with props and data is property of it. so you have to use it like
const PostsRow = ({data}) => {
And you've to convert your data to array like
const data = [{
title: "Ersti",
imagesrc: "./490.jpg",
url: "/meineposts"
}];

React Render List Incorrect (List is correct, but render overwrite and duplicate)

I’m trying to make a simple order system, when user selects QTY and click “Add to Order”, the information of the selected dish will be shown in the “Your Order” area (click “Refresh Order” to see). What I’m doing is to insert the dish information (from MenuEdit component) into a list (in CustomerOrder component).
My question is as following: I add “Moo Goo Guy Pan” QTY:1 to order, it will be inserted to the list, and then I add “Teriyaki Chicken” QTY:2, it will be inserted to the list, and I add “Moo Goo Guy Pan” again but QTY:3 to order, it will be inserted to the list. The uid is a timestamp when user click “Add to Order”. The list is working fine (I use lodash to deep copy), but when render all the item of the list is not good. I cannot think of a proper way to solve it. I’m willing to provide more information as you request. Thank you for your time and help.
I made a GIF to demonstrate the step and you can see the list changing in the console.
link: https://github.com/Dungyichao/COVID19_Reporting_Web/blob/master/img/EggrollChenOrderSC.gif
User click the “Add to Order”, the first and the second step are doing good.
The render steps of the Cart (Your Order), the first and the second step are doing good.
The following is the list that is passed to the component for rendering. Note the qty is different, and this is the correct list which I want and already passed into CartList components.
My Codes and Components structure is as follow
Order.js
export default class CustomerOrder extends Component {
constructor(props) {
super(props);
this.firebase = props.firebase;
this.sendinfo_toCart_handle = this.sendinfo_toCart_handle.bind(this);
this.Cart_new_item_arrive = this.Cart_new_item_arrive.bind(this);
this.remove_item = this.remove_item.bind(this);
this.state = {
Cart_data: [],
Cart_new_item: '',
}
this.refresh_cart_handle = this.refresh_cart_handle.bind(this);
}
remove_item(e){
console.log("Remove item uid: ", e);
}
refresh_cart_handle(){
let {Cart_data} = this.state;
console.log("Current Cart Data: ", Cart_data);
}
Cart_new_item_arrive(e){
//console.log("Cart_new_item_arrive: ", e);
this.setState({Cart_new_item: e}, () => {
//after setstate
this.sendinfo_toCart_handle();
});
}
sendinfo_toCart_handle(){
let {Cart_new_item, Cart_data} = this.state;
let deepcopy_list = _.cloneDeep(Cart_data);
deepcopy_list.push(Cart_new_item);
this.setState({Cart_data: deepcopy_list});
}
render() {
let {Cart_data} = this.state;
return (
<div style={order_style}>
<div style={{
}}>
<h3>Menu</h3>
<MenuEdit firebase={this.firebase} CartAdd={this.Cart_new_item_arrive} />
</div>
<div style={{
}}>
<h3>Your Order</h3>
<Cart data_array={Cart_data} remove_item={this.remove_item} /> {/*remove_item={this.remove_item}*/}
<Button onClick={this.refresh_cart_handle}>Refresh Order</Button>
</div>
</div>
);
}
}
Cart.js
export default class Cart extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>
<Scroll>
<CartList data_array={this.props.data_array} remove_item={this.props.remove_item} /> {/*remove_item={this.props.remove_item}*/}
</Scroll>
</div>
<div>
<div><Button>Refresh</Button></div>
<div>
<Button>Cancel</Button> {' '}
<Button>Place Order</Button>
</div>
</div>
</div>
);
}
}
CartList.js
export default class CartList extends Component {
constructor(props) {
super(props);
}
render(){
let display_data = this.props.data_array;
let null_page = [];
console.log("Data in CartList.js: ", display_data)
if(display_data[0]){
return(
display_data.map(
(cart_item, idx) => {
//console.log("In Map", idx, ' Item: ', cart_item);
return(
<div>
<CartItem key={idx} u_key={idx + 1}
Cart_item_info={cart_item}
remove_item={this.props.remove_item} /> {/*remove_item={this.props.remove_item}*/}
</div>
);
}
)
)
}
else{
return(
<div>
<p>Add Your Favorite Dishes to Here</p>
</div>
);
}
}
}
CartItem.js (which render each item in the list)
export default class CartItem extends Component {
constructor(props) {
super(props);
this.state = {
show_toggle: true,
cart_item_info: this.props.Cart_item_info,
u_key: this.props.u_key,
}
//cart_item_info: this.props.Cart_item_info,
this.remove_item_handle = this.remove_item_handle.bind(this);
}
remove_item_handle(){
let {cart_item_info} = this.state;
this.props.remove_item(cart_item_info.uid);
}
render() {
let {cart_item_info, u_key} = this.state;
//console.log("Return CartItem")
return (
<div key={u_key} >
<Accordion>
<Card>
<Accordion.Toggle as={Card.Header} eventKey="0">
<div style={item_style}>
<div style={{flex: '1'}}>{u_key}.</div>
<div style={{flex: '7'}}> {cart_item_info.dish_name}</div>
<div style={{flex: '2'}}>X {cart_item_info.qty}</div>
<div style={{flex: '2'}}>${cart_item_info.Tprice}</div>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey="0">
<Card.Body>
<Button onClick={this.remove_item_handle} >Remove</Button>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</div>
);
}
}
Update your cartItem.js file. It should be functional component.
const CartItems = (props) => {
console.log(props.Cart_item_info);
return (
<div key={props.u_key} >
<Accordion>
<Card>
<Accordion.Toggle as={Card.Header} eventKey="0">
<div>
<div style={{ flex: '1' }}>{props.u_key}.</div>
<div style={{ flex: '7' }}> {props.Cart_item_info.dish_name}</div>
<div style={{ flex: '2' }}>X {props.Cart_item_info.qty}</div>
<div style={{ flex: '2' }}>${props.Cart_item_info.Tprice}</div>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey="0">
<Card.Body>
<div>No</div>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</div>
);
}
I finally find the root cause of the problem which I didn't reveal it in the question because I don't even think that would cause a problem
The problem is from the MenuItem component (Menu item which user can click Add to Order). The correct way is to clear all data (which is going to insert to list in Order.js) hold in the state of the MenuItem component whenever it has already already inserted. By doing so you can prevent any original data being sent again.
Also in the chat room with Deepak, he suggest some following tips:
When you want to just show something and no state management than prefer functional component.
key help react to identity the item change in the list.

React Display component after click on card

I creating simple app. It must be app with information about pokemons. So I need to create, when user click on pokeCard, Sidebar info.
How it look now:
So, Sidebar is must to be, it can be, for example, only white background.
I think about styled-components, but I not sure, that this would be the right decision
How to do it with functional Component?
Wrapper
const [SelectedPokemonIndex, setSelectedPokemonIndex] = useState();
return (
<Row>
<Col xs={24} sm={14} lg={16}>
<Pokemons
PokemonsList={PokemonsList}
loadMoreItems={loadMoreItems}
Loading={Loading}
onClickPoke={(pokemonId) => {
fetchPokemonDetails(pokemonId);
fetchPokemon(pokemonId);
fetchPokemonStats(pokemonId);
setSelectedPokemonIndex(pokemonId);
}}
/>
</Col>
<Col xs={24} sm={10} lg={8}>
<About
pokemon={SelectedPokemon}
PokemonTypes={PokemonTypes}
PokemonStats={PokemonStats}
index={SelectedPokemonIndex}
LoadingForSelectedPokemon={LoadingForSelectedPokemon}
/>
</Col>
</Row>
);
}
export default Wrapper;
Child component of wrapper
function Pokemons(props) {
let { PokemonsList, loadMoreItems, Loading, onClickPoke } = props;
return (
<GridCard
image={`${IMAGE_BASE_URL}${++index}.png`}
pokemonId={index}
pokemonName={pokemon.name}
pokemonUrl={pokemon.url}
onClickPoke={onClickPoke}
/>
PokeCard
import React, { useEffect, useState } from "react";
import { Col, Typography } from "antd";
import "./GridCards.css";
const { Title } = Typography;
function GridCards(props) {
let { key, image, pokemonName, pokemonUrl, pokemonId } = props;
return (
<Col
key={key}
lg={8}
md={12}
xs={24}
onClick={() => {
props.onClickPoke(pokemonId);
}}
>
<div
className="poke-card"
}}
>
<img alt={pokemonName} src={image} />
{LoadingForPokemon && <div>Loading...</div>}
</div>
</Col>
);
}
export default GridCards;
This is Sidebar, what must to be change:
function About(props) {
let {
pokemon,
LoadingForSelectedPokemon,
index,
PokemonTypes,
PokemonStats,
} = props;
return (
<div
style={{
position: "sticky",
top: 0,
display: "flex",
justifyContent: "center",
}}
>
<PokemonDetails
pokemonName={pokemon.name}
pokemonId={pokemon.id}
pokemon={pokemon}
LoadingForSelectedPokemon={LoadingForSelectedPokemon}
image={`${IMAGE_BASE_URL}${index}.png`}
PokemonTypes={PokemonTypes}
PokemonStats={PokemonStats}
/>
</div>
);
}
This is a pretty broad question and there's a lot going on in your code, but I think you need to move some state management around.
In your GridCards component, give it a prop called onCardClick and call that function in the onClick of the <Col> component you're using. It'll look something like this:
function GridCard(props) {
const { key, image, pokemonName, pokemonUrl, pokemonId, onCardClick } = props;
return (
<Col
key={key}
lg={8}
md={12}
xs={24}
onClick={() => onCardClick()}
>
<div
className="poke-card"
>
<img alt={pokemonName} src={image} />
{LoadingForPokemon && <div>Loading...</div>}
</div>
</Col>
);
}
export default GridCard;
Then in your wrapper components, instead of using the Pokemons component, I think you can just use your GridCard component and map on whatever data you're using to render out the cards currently.
So something like this:
export default function Wrapper() {
const [selectedPokemon, setSelectedPokemon] = useState(null);
// data is whatever you have to iterate over to put data into your cards
const pokemonCards = data.map((p, idx) => {
return (
<GridCard
key={idx}
image={p.image}
pokemonName={p.pokemonName}
onCardClick={() => setSelectedPokemon(p)}
></GridCard>
);
});
return (
<Row>
<Col xs={24} sm={14} lg={16}>
{pokemonCards}
</Col>
<Col xs={24} sm={10} lg={8}>
<About pokemon={selectedPokemon} />
</Col>
</Row>
);
}
Basically, what you should try and accomplish is letting each GridCard component contain all the necessary information for your About component, so that when you click on a GridCard your Wrapper's state can update with a selectedPokemon and then pass that back down into your About component.
I found solution for my question.
Its only need to set isAboutShown
const [isAboutShown, setAboutShow] = useState(false);
And onClick I coding setAboutShow(true)
How to display component?
{isAboutShown && (
<About/>)

How do I clear the the array of a state?

So this is my code :
import React from "react";
import Navigation from './Navigation';
import Foot from './Foot';
import MovieCard from './MovieCard';
class Favorites extends React.Component {
render() {
const { onSearch, favorites, favoriteCallback, totalFavorites, searchKeyUpdate } = this.props;
return (
<div>
<Navigation
onSearch={onSearch}
totalFavorites={totalFavorites}
searchKeyUpdate={searchKeyUpdate} />
<div className="container">
<button onClick={()=> this.clearFavorites(favorites)}> Clear all movies </button>
{(favorites.length < 1) ?
<h1 style={{ fontSize: '13px', textAlign: 'center' }}>Please mark some of the movies as favorites!</h1>
:
<ul
className="movies">
{favorites
.map(movie => (
<MovieCard
movie={movie}
key={movie.imdbID}
toggleFavorite={favoriteCallback}
favorites={favorites}
/>
))}
</ul>
}
<Foot />
</div>
</div>
);
}
}
const clearFavorites = (favorites) => {
this.setState({ favorites: [] });
}
The thing I need for the button to do is that when i click it that it clears the whole state of favorites. The clearFavorites function is used to clear everything but when I try this I get an error:
Why doesn't this clear the state of favorites?
You have two problems:
clearFavorites function is not in your class. So you should put it inside.
You are trying to clear the data inside the favorites array, which is not part of your state, using the function clearFavorites. So, first of all, you should add favorites array to your state and then you can manipulate the information. I suggest you to use the function getDerivedStateFromProps.
As others mentioned, first moving clearFavorites function into Favorites class.
Second, your favorites list is not part of state object, but instead you pull it out from this.props.favorites, so instead of using this.setState, we should just change the props value.
Third, since you're emptying the array, the parameter in your clearFavorites probably not needed? Please refer to below:
First we define a constructor to get the value from props and pass it to state in the constructor as below:
constructor(props) {
super(props);
this.state = {favorites: this.props.favorites}
}
clearFavorites = () => {
this.setState({favorites: []});
};
Then at last in your render method change to following:
const { onSearch, favoriteCallback, totalFavorites, searchKeyUpdate } = this.props;
const favorites = this.state.favorites;// Or in your ul tag, instead of using favorites, change it to this.state.favorites
You can try to move the clearFavorites into your component
import React from "react";
import Navigation from "./Navigation";
import Foot from "./Foot";
import MovieCard from "./MovieCard";
class Favorites extends React.Component {
render() {
const {
onSearch,
favorites,
favoriteCallback,
totalFavorites,
searchKeyUpdate
} = this.props;
return (
<div>
<Navigation
onSearch={onSearch}
totalFavorites={totalFavorites}
searchKeyUpdate={searchKeyUpdate}
/>
<div className="container">
<button onClick={() => this.clearFavorites(favorites)}>
{" "}
Clear all movies{" "}
</button>
{favorites.length < 1 ? (
<h1 style={{ fontSize: "13px", textAlign: "center" }}>
Please mark some of the movies as favorites!
</h1>
) : (
<ul className="movies">
{favorites.map(movie => (
<MovieCard
movie={movie}
key={movie.imdbID}
toggleFavorite={favoriteCallback}
favorites={favorites}
/>
))}
</ul>
)}
<Foot />
</div>
</div>
);
}
clearFavorites = favorites => {
this.setState({ favorites: [] });
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories

Resources