Show a dummy text until image load in react - reactjs

I have a React Component, and a JSON which consists of image url, i pass the image url into image tag in my component through mapping function.
Here is a Example of my Code:
import imgDetails from "../data/ImageDetails";
class Example extends Component {
constructor(props) {
super(props);
this.state = {
imgContentLoad: false
}
}
imageHandleLoad() {
this.setState({
imgContentLoad: true
})
}
render() {
return ({
imgDetails.map((imgDetails) => {
return ( <
React.Fragment > {
(this.state.imgContentLoad &&
<
img src = {
imgDetails.imgURL
}
onLoad = {
this.imageHandleLoad.bind(this)
}
/>) || <
div > image is loading < /div>
} <
/React.Fragment>
)
}
)
}
Here i want to display the "image is loading" text to show until the image loads so i wrote the above image onload function code. but my problem is, "image is loading" is showing infinitely the image is not showing. what is wrong here?

extract it to a new component that will listen to the onload event and return the img when its loaded
class ImageWithLoading extends React.Component {
state = { isLoaded: false }
componentDidMount() {
const image = new Image();
image.onload = () => this.setState({ isLoaded: true });
image.src = this.props.src;
}
render() {
const { src } = this.props;
const { isLoaded } = this.state;
return isLoaded
? <img src={src} />
: <div>Loading image...</div>
}
}
Then make the Example component just a container that maps the data to the ImageWithLoading component
const Example = () => imgDetails
.map(({ imgURL }) => <ImageWithLoading key={imgURL} src={imgURL} />);

Related

Show a react-placeholder skeleton until a image loaded completely in a page

I'm Beginner in react, i want to show a skeleton placeholder until a image loads completely in my page so I used react-placeholder library for skeleton loader and set the loaded state to false so the skeleton loader shows.
I then change the loaded to true by setState it on onLoad function on img tag but my problem is the state is not changing so the skeleton loader shows infinitely thus hiding the image.
how to fix this? what i done wrong here? Someone please help me solve this.
import React from 'react';
import ReactPlaceholder from 'react-placeholder';
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
}
}
handleImageLoaded() {
this.setState({ loaded: true });
}
render() {
return (
<React.Fragment>
<ReactPlaceholder type = 'rect' ready = {this.state.loaded} style = {{height: 160}} >
<div className = "imageHolder" >
<img src ="http://someImage.png" alt = "" onLoad={this.handleImageLoaded.bind(this)} />
</div>
</ReactPlaceholder>
</React.Fragment>
)
}
}
"react-placeholder" is not rendering the image until state.loaded is true, so the onLoad callback will never be executed.
You should take the control of rendering the placeholder and control the image visibility by yourself, and do not use the "react-placeholder" component as it requires a children node, which is not the best election for this case.
import React, { Fragment } from "react";
import ImagePlaceholder from "./ImagePlaceholder";
class Image extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
}
}
handleImageLoaded() {
this.setState({ loaded: true });
}
render() {
const { loaded } = this.state;
const imageStyle = !loaded ? { display: "none" } : {};
return (
<div className = "imageHolder" >
{!loaded && <ImagePlaceholder/> }
<img src ="http://someImage.png" style={imageStyle} onLoad={this.handleImageLoaded.bind(this)} />
</div>
)
}
}
Anyway, if you still want to continue using "react-placeholder", then you could solve it with something like:
import React, { Fragment } from "react";
import ReactPlaceholder from "react-placeholder";
class Image extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
}
}
handleImageLoaded() {
this.setState({ loaded: true });
}
render() {
const { loaded } = this.state;
const imageStyle = !loaded ? { display: "none" } : {};
const image = <img src="http://someImage.png" style={imageStyle} onLoad={this.handleImageLoaded.bind(this)} />;
return (
<Fragment>
<ReactPlaceholder type="rect" ready={this.state.loaded} style={{height: 160}} >
<div className="imageHolder" >
{image}
</div>
</ReactPlaceholder>
{!loaded && image}
</Fragment>
)
}
}

React shows a stale state in the render

I have an Image class which allows me to change images from the containiner Component and update the image style.
My Class:
import React from "react";
import Radium from 'radium';
class StateImage extends React.Component {
constructor(props) {
super(props);
this.state = {
images: this.props.images.map(image => ({
...image,
loaded: false,
activeStyle: {visibility: 'hidden'}
})),
activeMode: props.activeMode
};
this.state.images.forEach((image, index) => {
const src = image.image;
const primaryImage = new Image();
primaryImage.onload = () => {
const images = [...this.state.images];
images[index].loaded = true;
if (images[index].name === this.state.activeMode) {
images[index].activeStyle = images[index].style;
// is this image the default activated one? if so, activate it now that it's loaded.
images[index].onActivate();
} else
images[index].activeStyle = {visibility: 'hidden'};
this.setState( {
...this.state,
images
});
};
primaryImage.src = src;
});
}
updateImageStyle = (name, style) => {
let images = [...this.state.images].map( (image) => {
if (image.name === name) {
return {
...image,
style: style,
activeStyle: style
}
} else return image;
});
this.setState({
...this.state,
images: images
}, () => {
console.log("updated state");
console.log(this.state);
});
};
onClick = () => {
this.state.images.map( (image) => {
if (image.clickable && image.name === this.state.activeMode)
this.props.eventHandler(this.state.activeMode);
});
};
render () {
console.log("render");
console.log(this.state.images);
let images = this.state.images.map((image, index) => {
return <img
key={ index }
onClick={ this.onClick }
style={ image.activeStyle }
src={ image.image }
alt={ image.alt}/>
});
return (
<div>
{images}
</div>
);
}
}
export default Radium(StateImage);
My problem revolves around updateImageStyle. When this function is called I need to change the style element of the active image and re-render so that users see the change.
updateImageStyle is reached, and I update the images portion of my state. I console.log it once the setState is done and I can verify the change was made correctly!
However, I also console.log from the render and to my amazement, the this.state.images outputted from render is stale and does not reflect my changes.
How can this be? the console.log proves the render that has the stale state is called AFTER I have confirmed my changes have taken place.
My console log:
You are most likely seeing your state being overwritten by a different setState call perhaps the one in primaryImage.onload. Since React batches setState calls together, render() is called only once with the updates.

How do I manage my array of children components' states?

I'm new to react, so forgive me. I'm having a problem understanding states, specifically those of children.
Purpose: I'm trying to create a form that a user can append more and more components -- in this case, images.
What happens: User appends 2 or more images. User tries to upload an image with UploadButton component, but both the images are the same. I believe this has to do with both appended children sharing the same state.
Question: How do I give each appended child its own image without affecting the other appended children?
class Page extends Component
constructor (props) {
super(props);
this.state = {
id: '',
numChildren: 0,
images: [],
}
this.onAddChild = this.onAddChild.bind(this);
}
showModal() {
this.setState({
numChildren: 0,
images: [],
});
}
renderModal()
const children = [];
//Here's my array of child components
for(var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent key={i} />);
}
return (
<ReactModal>
<this.ParentComponent addChild={this.onAddChild}>
{children}
</this.ParentComponent>
</ReactModal>
)
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
})
}
ParentComponent = (props) => (
<div>
{props.children}
<Button onClick={props.addChild}>Add Item</Button>
</div>
);
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={this.state.images}
onUploadComplete={uri => this.setState({images: uri})}
/>
</div>
);
}
Here's the code for UploadButton:
import React, { Component } from 'react';
import uuid from 'uuid';
import firebase from '../config/firebase';
class UploadButton extends Component {
constructor(props) {
super(props);
this.state = {
isUploading: false
}
}
handleClick() {
const input = document.createElement("INPUT");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/gif, image/jpeg, image/png");
input.addEventListener("change", ({target: {files: [file]}}) => this.uploadFile(file));
input.click();
}
uploadFile(file) {
console.log('F', file);
const id = uuid.v4();
this.setState({ isUploading: true })
const metadata = {
contentType: file.type
};
firebase.storage()
.ref('friends')
.child(id)
.put(file, metadata)
.then(({ downloadURL }) => {
this.setState({ isUploading: false })
console.log('Uploaded', downloadURL);
this.props.onUploadComplete(downloadURL);
})
.catch(e => this.setState({ isUploading: false }));
}
render() {
const {
props: {
value,
style = {},
className = "image-upload-button",
},
state: {
isUploading
}
} = this;
return (
<div
onClick={() => this.handleClick()}
className={className}
style={{
...style,
backgroundImage: `url("${this.props.value}")`,
}}>
{isUploading ? "UPLOADING..." : !value ? 'No image' : ''}
</div>
);
}
}
export default UploadButton;
I tried to exclude all unnecessary code not pertaining to my problem, but please, let me know if I need to show more.
EDIT: This is my attempt, it doesn't work:
//altered my children array to include a new prop
renderModal() {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent imageSelect={this.onImageSelect} key={i} />);
}
//...
};
//my attempt to assign value and pass selected image back to images array
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={uri => this.props.onImageSelect(uri)} //my greenness is really apparent here
onUploadComplete={uri => this.setState({images: uri})}
/>
//...
</div>
);
//added this function to the class
onImageSelect(uri) {
var el = this.state.images.concat(uri);
this.setState({
images: el
})
}
I know I'm not accessing the child prop correctly. This is the most complexity I've dealt with so far. Thanks for your time.
When you write this.state in Child / Parent component, you are actually accessing the state of Page. Now, I would recommend that you pass in the index of the child to the Child like so
children.push(<this.ChildComponent key={i} index={i}/>)
so that each children deals with only its own image like so
ChildComponent = ({index}) => (
<div>
<UploadButton
storage="menus"
value={this.state.images[index]}
onUploadComplete={uri => {
let images = this.state.images.slice()
images[index] = uri
this.setState({images})
}}
/>
</div>
);

Why preload image don't work?

I wrote a component for preload images. Every image have got a lite version (prop previewUrl) and normal version (prop src)
It seems, everything should be ok, but it's not!. Event onload doesn't work. GET request doesn't happend. I break my mind and don't know where the misstake.
Here is a codepen https://codepen.io/fsdev/pen/bLWgoL of the code below
class Image extends React.Component {
constructor(props){
super(props);
this.state = { src: null };
this.preloadImage = this.preloadImage.bind(this);
}
componentWillMount(){
const { src: bigImage, previewUrl } = this.props;
this.setState({src: previewUrl});
this.preloadImage(bigImage)
.then(() => {
console.log("Loaded");
this.setState({src: bigImage});
})
.catch(e => console.error(e.message))
}
preloadImage(url){
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = url;
})
}
render(){
const { src } = this.state;
const { previewUrl, ...rest } = this.props;
return(
<img {...rest} src={src} />
)
}
}
class App extends React.Component {
render(){
return(
<Image src="http://res.cloudinary.com/playhs/image/upload/v1518304840/bsyjqxahzr8bmaxuvj04.png"
previewUrl="http://res.cloudinary.com/playhs/image/upload/q_30/v1518304850/ztpj7gnqazk6ng2tzamk.jpg"
/>
)
}
}
ReactDOM.render(
<App/>, document.body
)
Thank you
I'am stupid
My React Component called as Image
and here
const image = new Image();
I call here my own Component :) lol

React - setState inside componentDidMount

I am building a lazyloading component for images. But I have a problem with setting state. I'm getting Can only update a mounted or mounting component error, but I am using setState inside componentDidMount, which should allow me to avoid such errors.
Here's my code:
export default class Images extends React.Component {
constructor(props) {
super(props);
this.element = null;
this.state = {
loaded: false,
height: 0
};
}
componentDidMount() {
this.element = findDOMNode(this);
this.loadImage();
}
getDimensions() {
const w = this.element.offsetWidth;
let initw = 0;
let inith = 0;
let result;
const img = new Image();
img.src = this.props.url;
img.onload = (e) => {
initw = e.path[0].width;
inith = e.path[0].height;
result = (w / initw) * inith;
setTimeout(() => {
this.setState({
loaded: true,
height: `${result}px`
});
});
}
}
loadImage() {
_scrolling.add([this.element], () => {
if (this.element.classList.contains(_scrolling.classes.coming)) { // the image is visible
this.getDimensions();
}
});
}
render() {
const classes = this.state.loaded ? `${this.props.parentClass}__image--loaded` : null;
const styles = this.state.loaded ? {
maxHeight: this.state.height, minHeight: this.state.height, overflow: 'hidden'
} : null;
return (
<div className={`${this.props.parentClass}__image ${classes}`} style={styles}>
{this.state.loaded ?
<img
className={`${this.props.parentClass}__img`}
src={this.props.url}
title={this.props.title}
alt={this.props.title}
/>
: null
}
</div>
)
}
I belive the problem lies within img.onload, but I don't know how to achieve this otherwise. What should I do?
If you attempt to set state on an unmounted component, you’ll get an error like that.There are two solutions:
Assure Component isMounted : use setstate(); after checking that the component is mounted or not.
Abort the Request: When the component unmounts, we can just throw away the request so the callback is never invoked. To do this, we’ll take advantage of another React lifecycle hook, componentWillUnmount.
It seems that the img.onload handler is getting called on an unmounted Images component instance.
Image loading is asynchronous and takes some time. When it’s finally done and img.onload handler gets called, there is no guarantee your component is still mounted.
You have to make use of componentWillUnmount and make sure you either:
Cancel the image loading before component gets unmounted, or
Keep the track of the component’s mounted state and check if it’s mounted once your handler gets called
More about checking if a component is mounted or not: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
Solution: Cancel The Image Loading
export default class Images extends React.Component {
constructor(props) {
super(props);
this.element = null;
this.state = {
loaded: false,
height: 0
};
this.images = []; // We’ll store references to the Image objects here.
}
componentDidMount() {
this.element = findDOMNode(this);
this.loadImage();
}
componentWillUnmount() {
this.images.forEach(img => img.src = ''); // Cancel the loading of images.
}
getDimensions() {
const w = this.element.offsetWidth;
let initw = 0;
let inith = 0;
let result;
const img = new Image();
img.src = this.props.url;
img.onload = (e) => {
initw = e.path[0].width;
inith = e.path[0].height;
result = (w / initw) * inith;
setTimeout(() => {
this.setState({
loaded: true,
height: `${result}px`
});
});
}
this.images.push(img); // Store the reference.
}
loadImage() {
_scrolling.add([this.element], () => {
if (this.element.classList.contains(_scrolling.classes.coming)) { // the image is visible
this.getDimensions();
}
});
}
render() {
const classes = this.state.loaded ? `${this.props.parentClass}__image--loaded` : null;
const styles = this.state.loaded ? {
maxHeight: this.state.height, minHeight: this.state.height, overflow: 'hidden'
} : null;
return (
<div className={`${this.props.parentClass}__image ${classes}`} style={styles}>
{this.state.loaded ?
<img
className={`${this.props.parentClass}__img`}
src={this.props.url}
title={this.props.title}
alt={this.props.title}
/>
: null
}
</div>
)
}
}
I copied the image cancelling from: https://stackoverflow.com/a/5278475/594458

Resources