Display Multiple Images from Json in ReactJS - javascript

I've managed to create a mockup json that i need to test a json request via axios on a react app.
For now, i can console.log the json file structure and can assign the data for the link.
The problem is that my content it's not being rendered correctly in the DOM via Map Method. the images are not appearing.
import {Link} from 'react-router-dom';
import axios from 'axios';
class DesignItem extends Component {
state = {
isLoading: true,
designs: [],
error: null
}
componentDidMount () {
axios.get('http://www.mocky.io/v2/5dadd81c2d0000e0f5e4bd57')
.then (res => {
console.log(res.data);
const designs = res.data;
this.setState({designs})
})
}
render() {
return (
<React.Fragment>
{this.state.designs.map(designs => (
// this one is appearing right as expected
<Link to={designs.productPage}>
<div className="design-item" key={designs.id}>
// this image doesn't appear. the URL is there but the image it's broken
<img src={designs.featUrl} alt="" />
</div></Link>
))}
</React.Fragment>
);
}
}
export default DesignItem;```

<React.Fragment>
{this.state.designs.map(designs => (
<Link to={designs.productPage} key={designs.id}> // I think the key must be put here instead on the div
<div className="design-item">
<img src={designs.featUrl} alt="" />
</div>
</Link>
))}
</React.Fragment>
Also upon checking the data, the image source was like this:
../images/products/alexandre-iii/0.jpg
Maybe that is why it is not showing up, if you could change the url to something like:
https://your-domain.com/public/images/products/alexandre-iii/0.jpg
It will show up.

Related

Forwarding props from parent to child component

I have 2 components list of posts and when clicking on link on post card i'm entering into post.
I can't access props.postDetails in child component. When I console log the props, I have {history: {…}, location: {…}, match: {…}, staticContext: undefined} only this without props.postDetails.
Can somebody help?
Code for parent component is:
mport {useState, useEffect} from 'react';
import {BrowserRouter as Router, Switch, Route, Link, withRouter} from "react-router-dom";
import logo from "./assets/images/logo.jpg";
import Post from './Post';
const Home = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
getResults();
},[]);
const getResults =() => {
fetch("https://blog-d8b04-default-rtdb.europe-west1.firebasedatabase.app/posts.json")
.then(response => response.json())
.then(data => {setPosts(data)});
}
const postsArr = [];
Object.values(posts).forEach((post, key) => {
postsArr.push(post);
});
return(
<div>
<div className="container-fluid">
<div className="row">
<div className="posts-container col-md-12">
<div className="row">
{
postsArr.map((post, key) => (
<div className="col-md-4">
<Link to={`/post/${key}`} >
<div className="pic-wrapper">
<img className="img-fluid" src={post.pic} alt={post.title}/>
</div>
<h4>{post.title}</h4>
<Post postDetails={post}/>
</Link>
</div>
))
}
</div>
</div>
</div>
</div>
</div>
)
}
Code for child component:
import {withRouter} from "react-router-dom";
const Post = (props) => {
const {pic, title, author, description} = props.postDetails;
return(
<div className="container">
<div className="pic-wrapper">
<img className="img-fluid" src={pic} alt={title}/>
</div>
<h4>{title}</h4>
<p>{author}</p>
</div>
)
}
export default withRouter(Post);
Issue
Ok, it's as I started to suspect. You are rendering a Post component in more than 1 place.
The issue here is that in Home.js you are passing a postDetails prop, (<Post postDetails={post.pic} />), but in app.js you are only passing the route props from Route, (<Route path="/post/:postId" exact strict component={Post} />). This Post component is the one triggering the error.
Solution
An easy solution is to simply pass the post data along with the route transition.
<Link
to={{
pathname: `/post/${key}`,
state: {
post
}
}}
>
...
<Post postDetails={post.pic} />
</Link>
And access the route state on the receiving end in Post. Try to read the post details from props first, and if they is falsey (null or undefined) assume it was passed in route state and access it there.
const Post = (props) => {
const { state } = props.location;
const { pic, title, author, description } = props.postDetails ?? state.post;
return (
<div className="container">
<div className="pic-wrapper">
<img className="img-fluid" src={pic} alt={title} />
</div>
<h4>{title}</h4>
<p>{author}</p>
</div>
);
};
Of course there is room to make this a bit more robust but this is a good start.
Additional Suggestion
Instead of saving post state that isn't formed correctly for what/how you want to render it, you can transform the response data before saving it into state. This save the unnecessary step of transforming it every time the component rerenders.
const getResults = () => {
setLoading(true);
fetch(
"https://blog-d8b04-default-rtdb.europe-west1.firebasedatabase.app/posts.json"
)
.then((response) => response.json())
.then((data) => {
setPosts(Object.values(data));
setLoading(false);
});
};
Then map as per usual. Make sure to place the React key on the outer-most mapped element, the div in your case.
{posts.map((post, key) => (
<div className="col-md-4" key={key}>
...
</div>
))}
Demo
That is indeed an expected behaviour, because you are actually mapping what appears to be an empty array - see postArr; on your first render it will result as an empty array and since that's not a state, it will never re render your child component with the appropriate props.
I don't really see why you fetch the data, set them to your posts useState and then copy them over to a normal variable; Instead, remove your postArr and on the map replace it with your posts directly.
Since that's a state, react will listen to changes and rerender accordingly, fixing your problem

Some data in an API call is returning Error in a react project

Please I don't know the reason why I'm getting '"URL of undefined" when accessing data in an API call? Presently, I'm working with an API in a react project and I needed to access an image URL in the data, Every other data I'm accessing works perfectly except the image URL.
codeSaandbox
Here is my working code except for the Image URL.
import React, { Component } from "react";
import axios from "axios";
export default class Cat extends Component {
state = {
data: [],
CatWeight: "",
CatMetric: ""
};
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
});
const [CatWeight, CatMetric] = this.statistics();
this.setState({
CatWeight,
CatMetric
});
} catch (error) {
console.log(error);
}
};
render() {
return (
<div>
<h2>Cat Assignmaent</h2>
{console.log(this.state.data)}
{this.state.data.map((item, id) => {
return (
<div key={id}>
{/* <img src={item.image.url} alt=""/> Please why is this giving url undefined ? */}
<h2> {item.name}</h2>
<h3> {item.origin}</h3>
<h2>{item.temperament}</h2>
<h2>{item.life_span} kg</h2>
<p>{item.description}</p>
</div>
);
})}
</div>
);
}
}
simply because you are trying to access URL property at the moment your data does not exist yet. and as you know Javascript is Synchronous,
you could fix it by using Optional chaining
<img src={item?.image?.url} alt=""/>
check this
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
While the explanation in the answer of #Swarup is correct, I would rather use this:
render() {
return (
<div>
<h2>Cat Assignmaent</h2>
{this.state.data.map((item, id) => {
return (
<div key={id}>
{
item.image
? <img src={item.image.url} alt={item.name} />
: 'No image, sorry'
}
<h2>{item.name}</h2>
<h3>{item.origin}</h3>
<h2>{item.temperament}</h2>
<h2>{item.life_span} kg</h2>
<p>{item.description}</p>
</div>
);
})}
</div>
);
}
Seems like this is an issue with the API. Some records don't image key in them. To verify this, add following lines after receiving response.
const filtered = data.filter(item => !item.image);
console.log(filtered);
To avoid this you can add a check before displaying an image.
{item?.image?.url && <><img src={item.image.url} alt=""/></> }
You are trying to access state data which is not exist yet, You must have initial state data that contains default URL for image, until you get image data from the API response, so by that you can avoid this problem,
and one thing i may suggest you need to have type safety for the response, so that you would know already what type of data you are going to get from the API response.

TypeError: Cannot read property of undefined using React

I'm new in React and I'm doing a little app with PokeAPI. I have a component called PokemonDetail in which I want to show the details of a pokemon, but the app throws me the next error
TypeError: Cannot read property 'front_default' of undefined
my component looks like this:
import React from "react";
const PokemonDetail = ({ pokemon }) => {
return (
<div>
<div className="text-center">{pokemon.name}</div>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
{pokemon.id}
</div>
);
};
export default PokemonDetail;
And the App component from which the PokemonDetail recive the prop of pokemon looks like this:
import React from "react";
import PokeAPI from "../apis/PokeAPI";
import SearchBar from "./SearchBar";
import PokemonDetail from "./PokemonDetail";
class App extends React.Component {
state = { pokemon: '' };
onTermSubmit = async term => {
try {
const response = await PokeAPI.get(`pokemon/${term}`);
this.setState({ pokemon: response.data });
console.log(response);
} catch (error) {
console.log("No existe");
}
};
render() {
return (
<div className="container">
<div className="row mt-3">
<div className="col">
<SearchBar onFormSubmit={this.onTermSubmit} />
</div>
</div>
<div className="row mt-3">
<div className="col-9" />
<div className="col-3">
<PokemonDetail pokemon={this.state.pokemon} />
</div>
</div>
</div>
);
}
}
export default App;
I don't understand why it throws me this error because only throws it with this and other properties of the json. With the name property works and wait until I send it some props, same with the id but no with the front_default property, which is a url of a image.
Because ajax is slower than react rendering, you can use a loading component before you get the data.
const PokemonDetail = ({ pokemon }) => {
if(pokemon.sprites == undefined){
return(
<div>
Loading...
</div>
);
}
return (
<div>
<div className="text-center">{pokemon.name}</div>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
{pokemon.id}
</div>
);
};
Very likely just an AJAX issue, your component renders before it has time to complete your request to the API. Try adding an additional check before rendering the image.
import React from "react";
const PokemonDetail = ({ pokemon }) => {
return (
<div>
<div className="text-center">{pokemon.name}</div>
{pokemon.sprites ? (
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
) : (
null
)
}
{pokemon.id}
</div>
);
};
export default PokemonDetail;
#ZHAOXIANLONG gave you the best solution (use a loading component until you receive data), but, if you do not use a loading component, you can use the get method from lodash library [1] in order to avoid a possible error.
import React from "react";
import _ from 'lodash';
const PokemonDetail = ({ pokemon }) => {
const front_default = _.get(pokemon, 'sprites.front_default', 'DEFAULT_VALUE');
const name = _.get(pokemon, 'name', 'DEFAULT_VALUE');
return (
<div>
<div className="text-center">{pokemon.name}</div>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
{pokemon.id}
</div>
);
};
export default PokemonDetail;
where the third parameter ('DEFAULT_VALUE') is a default value that will be used if the lodash can not retrieve a value for your query.
PS: I advise you to use lodash even in #ZHAOXIANLONG solution if you know that your API Server can be changed.
[1] https://lodash.com/docs/4.17.11#get
The initial state is { pokemon: '' }; pokemon is an empty string. PokemonDetail is referring to pokemon.sprites.front_default, but pokemon is initially a string and a string does not have a field called sprites.
If you are expecting pokemon to eventually become an object, you could initialize it to something that looks like an object:
state = { pokemon: { sprites: {front_default: '' }}};

React fetch img from slow api

I am sure that my question is obvious but I cannot find simple answer anywhere. I am not familiar with redux/flux so I don't know if I need to learn them to achieve my goal.
I get from my server urls to images I need to display on the component. I want to display loader till the image is fetched.
What is the best (and easiest) way to do that? Is necessary to use flux/redux?
May I use just fetch(image_URL).then... promise?
For now on just call url while rendering img html tag:
{this.props.data.images.map(img=>{
return(
<img src={img.url}/>
)})
how to manage async of this task? I already use apollo to fetch local db data. May I use apollo for fetching external data?
The easiest way is to define a loading flag and use it to determine if the loader should be rendered. It seems that your fetch logic somewhere else but the idea is the same.
class YourComponent() extends Component {
constructor() {
this.state = {
isLoading: false,
};
}
componentWillMount() {
this.setState({isLoading:true});
fetch('image_URL')
.then(res => {
this.setState({
images: res.images,
isLoading: false,
})
})
}
render() {
const { isLoading , images} = this.state;
if (isLoading) {
return <YourLoaderComponent />
}
return (
<div>
{images.map(img => <img src={img.url} />)}
</div>
);
}
}
You can make a use of onLoad react callback on the <img/> tag.
Tutorial:
Define React Component <LoadedComponent /> which will be a spinner.
Then you can define another React Component <ImageComponent /> which will have a default imageLoaded state set to false.
If imageLoaded is false, <ImageComponent/> will render img with width and height 0px.
The <img /> tag has onLoad binding to function imageLoaded() which then sets the imageLoaded state to true. When the state changes onLoad(when image finished loading) of <img/>, <ImageComponent/> automatically rerenders and it renders only <img/> with normal width and height.
import React from "react";
import { render } from "react-dom";
const LoaderComponent = () => (
<img
width="300"
height="300"
alt="Loading spinner"
src="http://blog.teamtreehouse.com/wp-content/uploads/2015/05/InternetSlowdown_Day.gif"
/>
);
const hiddenImageStyle = {
width: 0,
height: 0
};
class ImageComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
};
}
imageLoaded() {
this.setState({
loaded: true
});
}
render() {
if (this.state.loaded) {
return(
<div>
<img alt="Cat" src={this.props.url} width="300" height="300" />
</div>
)
}
return (
<div>
<img
alt="Cat"
src={this.props.url}
width="300"
height="300"
onLoad={() => this.imageLoaded()}
style={hiddenImageStyle}
/>
<LoaderComponent />
</div>
);
}
}
const imagesUrls = [
"https://c1.staticflickr.com/5/4200/34055408873_e9bf494e24_k.jpg",
"https://c1.staticflickr.com/5/4536/37705199575_ded3cf76df_c.jpg"
];
class App extends React.Component {
render() {
return (
<div>
{imagesUrls.map((url, index) => (
<ImageComponent key={index} url={url} />
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here you can see a working example: https://codesandbox.io/s/zwz9o84kn3
If you have a good Internet speed you will probably not notice the spinner. To see the spinner, the best is to open preview of the sandbox in a separate tab
and then open chrome dev tools and in the Network tab check disable cache and set a preset to Slow 3G
After refreshing you will notice loadining spinner, until the image will load

React-bootstrap styling showing up before finished api call

I am making a small application in React that fetches a random image using Axios. I am using React-bootstrap to style the image, however a small white box is displayed for half of a second before the image is done loading. How can I resolve this?
This is my code:
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { Image, Button } from 'react-bootstrap';
const ROOT_URL = 'myurl'
export default class WhatDog extends Component {
constructor(props){
super(props);
this.state = { randomImg: '' };
}
componentDidMount(){
axios.get(ROOT_URL)
.then(res => {
const data = res.data.message
this.setState({ randomImg: data })
})
}
renderImage(){
return(
<div>
<Image src={this.state.randomImg} className="img" thumbnail/>
<Link to="/">
<Button bsStyle="danger" bsSize="large">Go back</Button>
</Link>
</div>
)
}
render() {
return (
<div className="App">
{
(this.state.randomImg === '')
? <div>
<h1>Loading...</h1>
</div>
: <div>
{this.renderImage()}
</div>
}
</div>
);
}
}
The browser will fire the onLoad event, after the image has been, you name it.. loaded so before that, set the visibility to hidden.. NOT display: none ! Because display: none will also prevent the loading.
The solution might look something like this
<Image
src={this.state.randomImg}
style={!this.state.imgVisible ? {visibility: 'hidden'} : {}}
onLoad={() => this.setState({ imgVisible: true })}
/>
Note: This is using inline styles and arrow functions, which is not best, but for simplicity of the demo its enough, you could also you a className instead, its up to you ;)
You got the idea...

Categories

Resources