React fetch img from slow api - javascript

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

Related

Display Multiple Images from Json in ReactJS

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.

Load the component when all data has ready

I'm using React with Redux.
In this example I have my class with mapStateToProps and mapDispatchToProps
class EnigmaPage extends Component {
constructor(props){
super(props);
}
componentDidMount() {
this.props.authCheckState();
}
readUserData() {
this.props.loadLevel(this.props.userId);
}
render(){
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
<LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
</div>
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
authCheckState: () => dispatch(actions.authCheckState()),
getLevel: () => dispatch(actions.getLevel()),
loadLevel:(id) => dispatch(actions.loadLevel(id))
};
}
const mapStateToProps = state => {
return {
userId:state.auth.user,
level:state.level.level
}
}
I wanna push to my component LevelInfo the values difficulty and level but these 2 data arrive from getLevel() that is an http request with delay.
The page loads before receiving all the data from the http call.
I'm looking a way to wait to load the component LevelInfo or reload the component when the data are all ready.
You need to tell your component that will wait for the data needed to render your Level component, so into your EnigmaPage component write as follow
render(){
const { level } = this.props;
if (!level) { return <LoadingComponentSpinner />; } // this will render only when level attr is not set, otherwise will render your `LevelInfo` component
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
<LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
</div>
</div>
</div>
)
}
I hope it can help you.
We don't make our components wait, we let the initial rendering happens but render the target component with a conditional expression.
render() {
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
{this.props.level && (
<LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
)}
</div>
</div>
</div>
);
}
So, here we are checking if this.props.level is defined. When it is undefined React does not render anything, after getting the data LevelInfo component is rendered. You can change conditional rendering logic. You can render a Loading component maybe instead of nothing. But, at the end of the day, you will render your component conditionally.
try to use conditional rendering:
isDataReady() {
if(this.props.level.difficulty && this.props.level.level)
return true;
return false;
}
render(){
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
{this.isDataReady() ? <LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
: <div> </div>}
</div>
</div>
</div>
);
}
in case your data is not ready you can display anything you want, for simplicity here I just added an empty <div>.
Johuder Gonzalez has talked about the Spinner. The Material UI has a lot of examples, which is easy to apply. Please look the followings.
Material UI Progress

How to get an element By ID inside React.Fragment?

I am trying to catch an element using an ID in React, but I could not.
render() {
//Looping through all menus
let menuOptions = this.props.menuLists.map(menuList => {
return (
<li className="nav-item active" key={menuList.name}>
<a className="nav-link" href={menuList.anchorLink}>
{menuList.name}
</a>
</li>
);
});
return (
<React.Fragment>
<div id="animSec">
<canvas id="myCanvas" />
</div>
</React.Fragment>
);
}
I want to call the myCanvas ID.
I tried by this.refs, but it's sent me undefined. I also tried react-dom:
ReactDOM.findDOMNode(this.refs.myCanvas);
but get nothing. I call findDOMNode on the constructor first and I tried componentDidMount but get nothing.
You can use Callback Refs to retrieve the ID:
class YourComponent extends React.Component {
constructor(props) {
super(props)
this.canvas = null
}
componentDidMount() {
console.log(this.canvas.id) // gives you "myCanvas"
}
render() {
return (
<React.Fragment>
<div id="animSec">
<canvas id="myCanvas" ref={c => {this.canvas = c}}></canvas>
</div>
</React.Fragment>
)
}
}
alternatively, for React v16.3+, you can use createRef():
constructor(props) {
super(props)
this.canvas = React.createRef()
}
componentDidMount() {
console.log(this.canvas.current.id) // gives you "myCanvas"
}
render() {
return (
<React.Fragment>
<div id="animSec">
<canvas id="myCanvas" ref={this.canvas}></canvas>
</div>
</React.Fragment>
)
}
If you set up a ref in your constructor as this.canvasRef = React.createRef()
and apply it to your canvas as
<React.Fragment>
<div id="animSec">
<canvas ref={this.canvasRef}></canvas>
</div>
</React.Fragment>
You should be able to access the element directly. You can console log or check your dev tools to view the ref value. And (to my best knowledge), it's best practice to use Refs compared to querySelectors in React. You might also check out this post to see if you can work around with a method on canvas.
You can try using document.getElementById('myCanvas') in a custom function or any life cycle method to target the desired element.
Note that this doesn't work if you're doing SSR with a framework like next.js because there is no document object in the server.

How Can I use the returned string (new feature of React v16.0.0) in the source tag of an image ex: <img src='returnedString' />?

I want to use the new feature on React v16.0.0 for returning a string, then use that string in
<img src="returnedString" >
What is the current behavior?
Well if I render my component in a
<div > <MyComponent /> </div>
I can see the string displayed on the screen (see attached screenshot), but my goal is to use that string in <img src="returnedString" />
here is my code:
// My component that returns strings
class MyComponent extends Component {
render(){
switch (this.props.type) {
case 'text':
return this.props.json.data[this.props.Key]
break
case 'image':
return this.props.json.data[this.props.Key]
break
default:
return null
break
}
}
}
const UserComponent = (props) => {
return (
<div>
{/* this displays the string on the page */}
<MyComponent type='image' Key='avatar' desc='Abified image' {...props} />
{/* If I console.log this line I get <img src={[object object]} /> */}
<img src={<MyComponent type='image' Key='avatar' desc='Abified image' {...props} />} />
</div>
)
}
// Parent Component
class App extends Component {
constructor(props){
super(props)
this.state = {
json:[]
}
}
// Fetching data from an api
componentDidMount(){
fetch("https://reqres.in/api/users/2")
.then(response => response.json())
.then(json => {
this.setState({json: json })
})
}
render() {
return (
<div>
<UserComponent {...this.state}/>
</div>
);
}
}
export default App;
How Can I achieve that?
What is the expected behavior?
I want to use the returned string inside an
Which versions of React ?
React v16.0.0
Did this work in previous versions of React?
No because it's a new feature in React v16.0.0
If you want to set the image's src attribute dynamically, then use a plain javascript function instead of a component:
const getImageSrc = props => {
switch (props.type) {
case 'text':
case 'image':
return props.json.data[props.Key]
}
}
Then you can call it from your component's render method like this:
<img src={ getImageSrc({...this.props, type: 'image', Key: 'avatar'}) } />
Because this
<MyComponent type='image' Key='avatar' desc='Abified image' />
is creating a text node in the dom.
But in this case
<img src={<MyComponent type='image' Key='avatar' desc='Abified image' {...props} />
your src attribute is getting a react element object.
You'll never get a string like that.
If you want to get your src dynamically, try something like this
const UserComponent = (props) => {
// calculate you src here
const imageSrc = props.json.data['avatar'];
return (
<div>
<img src={ imageSrc } />
</div>
)
}
Hope this helps.

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