How to preload image in a React JS component? - javascript

I'm currently rendering a child component when a signInError occurs. The signInError is stored in the parent component and if it's not null, it renders the <SignInError/> component, as per the code below:
ParentComponent.js
// Some code...
render() {
return(
<div>
<SignInForm
doSignIn={this.doSignIn}
resetSignInError={this.resetSignInError}
signInError={this.state.signInError}
/>
{this.state.signInError && <SignInError/>}
</div>
);
}
So far, so good, here's the child component SignInError.js
import React from 'react';
import RoundImage from '../../../UI/Common/RoundImage';
import Newman from '../../../../assets/newman-min.png';
class SignInError extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
const img = new Image();
img.src = Newman;
}
render(){
return(
<div>
<div>
<RoundImage src={img.src}/> // <--- img is undefined here!!!
</div>
<div>
Hello... Newman!
</div>
</div>
);
}
}
export default SignInError;
RoundImage.js
import React from 'react';
const RoundImage = (props) => {
return (
<div>
<img src={props.src}/>
</div>
);
}
export default RoundImage;
How to preload images in React.js?
This question's answer (link above) here on Stack Over flow tells me to create the img object inside the componentDidMount() method to force the browser to load it. And so I did, as you can see from the code above. But now, when I try to pass it as a prop to my grand-child component inside my render method, I can't access the img, because it was defined inside of another method.
What's the best way around this? I just need the image to be loaded and to be displayed together with the error message. Otherwise the error message will show before the image, if your browser hasn't cached it yet. Thanks for the help.

Image download happens in the browser. Rendering to the DOM also happens in the browser.
By preloading, do you mean that you want that the component renders only when the image is ready?
If so, you could do something like this:
componentDidMount() {
const img = new Image();
img.onload = () => {
// when it finishes loading, update the component state
this.setState({ imageIsReady: true });
}
img.src = Newman; // by setting an src, you trigger browser download
}
render() {
const { imageIsReady } = this.state;
if (!imageIsReady) {
return <div>Loading image...</div>; // or just return null if you want nothing to be rendered.
} else {
return <img src={Newman} /> // along with your error message here
}
}

A bit of a different approach, but if you have the image source in advance, you can add the image source to the main html file as a link ref with preload option. The browser will preload the image (with relatively low priority) and at the time your app will load the image, it should be cached in the browser memory.
<head>
..
..
<link rel="preload" href="<your_image_source_here>" as="image">
...
</head>
In this approach, we separate the preloading process from the code. It is more relevant when you have the image source in advance (and not dynamically) and when you don't need to cache a large amount of images (will be a bit messy to add a large list of links in the html head, although possible)
you can learn more about link preloading here: Preloading content with rel="preload"

In my case I start with an initial src attribute for my images and wanted to delay changing them after the image has been loaded by the browser, so I wrote the following hook in Typescript:
import { useEffect, useState } from 'react';
export const useImageLoader = (initialSrc: string, currentSrc: string) => {
const [imageSrc, _setImageSrc] = useState(initialSrc);
useEffect(() => {
const img = new Image();
img.onload = () => {
_setImageSrc(currentSrc);
};
img.src = currentSrc;
}, [currentSrc]);
return [imageSrc];
};

Related

Why my component don't change even when render function it's being recalled? React

Im changing a child component state from its parent using this method:
From parent:
this.changeBottomToolbar(newTool)
Method declared on Child's class
changeBottomToolbar(newToolbarName){
this.setState({selectedToolbar: newToolbarName})
}
It's state change and it re-renders as I check with the console.
The render it's a conditional render that uses a function to get what it should render
getBottomToolbar(){
switch(this.state.selectedToolbar){
case "SelectorAndText":
console.log("Devolviendo el selector")
return <TextBottomToolbar
ref={this.bottomToolbarRef}
fontSizeUpdater = {this.fontSizeUpdater}
fontColorUpdater = {this.fontColorUpdater}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
fontFamilyUpdater = {this.fontFamilyUpdater}
alignmentUpdater = {this.alignmentUpdater}
/>
break
case "FreeLine":
console.log("Devolviendo el Line")
return <LineBottomToolbar
ref={this.bottomToolbarRef}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
shadowColorUpdater={this.shadowColorUpdater}
shadowSizeUpdater={this.shadowSizeUpdater}
/>
break
}
}
And i call it on the render:
render(){
const { classes } = this.props
console.log(this.state.selectedToolbar)
return (
<React.Fragment>
{
this.getBottomToolbar()
}
</React.Fragment>
);
}
As you can see in this image, the code it's been executed correctly and it returns the other component when the state it's changed
But the component ITS NOT CHANGING even tho the render it's been called again and it's state it's changing, im completely shocked, I have no clue on why this happens, please help!!
So the problem was that i wrongly copy-pasted the import (facepalm)
This was the import I was doing so I thought it wasn't chaning
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/TextBottomToolbar'
This corrected the problem:
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/LineBottomToolbar '

React - Dynamic image rendering stutters when bundled/live

I have an issue with a component that takes a name property for use as an avatar (replaces spaces with dashes and adds file format). This then gets used as the img src dynamically.
Works absolutely fine locally, no stuttering changing the image. When I bundle and deploy to surge.sh and change routes the image stutters. I expected this as each image needs to be downloaded initially but even after the image is cached, swapping the image is jerky. Any ideas? Or is there a better way to do this?
class Athlete extends Component {
constructor (props) {
super(props);
}
render () {
let name = this.props.name;
let imgName = name.replace(/\s+/g, '-').toLowerCase();
let imgSrc = '/img/athletes/' + imgName + '.jpg';
return (
<div className="winner__inner">
<img src={imgSrc} alt={name} className="winner__avatar"/>
<h3 className="winner__name">{name}</h3>
</div>
);
}
}
export default Athlete;
Link: http://boulder.surge.sh/ (desktop only - click one of the events)
GIF: https://i.gyazo.com/4dea5302d9671b4de0fadc2334f872da.gif
Cached images: https://i.imgur.com/xQLgIWq.png
Any help would be great, thanks!

Add fallback src to image on React during server fail (S3)

I have a React + Rails app and the current S3 server issues made me realize that I don't have a proper fallback mechanism when data can't be pulled from s3. I'm trying to make it work with my locally stored images for the moment and I plan on adding it the same way to my other image tags.
My img is as follows:
errorLink() {
this.onError = null;
this.src = '/img/icons/static/credentials.svg';
}
<img alt="Icon for credentials and experience" src="/img/icons/static/credentials123.svg" onError={this.errorLink.bind(this)()}/>
the src inside the image pointing to credentials123.svg is a dummy and I've added it specifically to raise the error. But it's not updating the src of my image. How can I achieve this in react? I'd rather not show broken image links to my users next time during an Amazon outage
One of the many many solutions (since it is really based on your inner React project structure). You just keep your image url in a state somewhere as a default image. Once you get your proper image url from S3 then you will replace default one in state with a new one you got.
const DEFAULT_IMAGE = '/img/icons/static/default.svg';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
url: DEFAULT_IMAGE
};
}
render() {
return (
{/* ... some UI here ... */}
<img
alt="Icon for credentials and experience"
src={this.state.url}
/>
);
}
// ....
_someAsync = () => {
// some async logic here
// In here in 5 seconds state will be updated by replacing default url
// with new url image link
setTimeout(() => this.setState({
loaded: true,
url: '/img/icons/static/credentials.svg'
}), 5000);
};
}
One simple way to do it is
Make sure you put/set the value this.state = {image: 'image-to-load.jpeg'} in render.
<img src={this.state.image} ref={image => this.image = image} onError={() => this.image.src = '/alternate-failover.jpg'
It worked for me. Small, precise, and reliable code
Ref: https://stackoverflow.com/a/42848501/10231115

React/webpack conditionally return require.ensure component (code splitting)

I have a sub component that does not need to be loaded immediately that I want to split out. I am trying to conditionally load in a react component via require.ensure. I am not getting any console errors but I am also not seeing anything being loaded. Here is the code I am calling :
renderContentzones() {
if (this.props.display ) {
return require.ensure([], () => {
const Component = require('./content-zones/component.jsx').default;
return (
<Component
content={this.props.display}
/>
);
});
}
return null;
}
It is just rendering a blank screen currently (no errors). This previously worked when I used import 'displayComponent' from './content-zones/component.jsx' and just returned it like you normally would in react, instead of this require.ensure but. Not sure what I am doing wrong here, any idea how to make something like this work? Thanks!
This is one way to do it, using the state to show the dynamic loaded component:
constructor(){
this.state = {cmp:null};
}
addComponent() {
const ctx = this;
require.ensure(['../ZonesComponent'], function (require) {
const ZonesComponent = require('../ZonesComponent').default;
ctx.setState({cmp:<ZonesComponent />});
});
}
render(){
return (
<div>
<div>Some info</div>
<div><button onClick={this.addComponent.bind(this)}>Add</button></div>
<div>
{this.state.cmp}
</div>
</div>
);
}
When you press the button add the component will be shown.
Hope this help.

Why can't I get the DOMNode's style (scrollHeight) in the React componentDidMount?

I am developping a React Application and trying to get the computed style 'scrollHeight' of a DOM Element.
I put this code in the componentDidMount:
componentDidMount() {
// let _this = this;
// window.onload = function(){
let imgFigureDOM = findDOMNode(_this.refs.imgFigure0),
imgW = imgFigureDOM.scrollWidth,
imgH = imgFigureDOM.scrollHeight;
// }
}
But, I can't get the correct value of scrollHeight only in the chrome browser.It seems that the chrome is not enough fast to render the DOMNode completely when the findDOMNode execute.
The value is correct when I use window.onload as above, but Shouldn't the DOMNode was completely loaded when the componentDidMount execute?
Thank you for your patient answer!
componentDidMount() is called when your React component is rendered. React has rendered an <img> tag, that doesn't mean that the image is loaded.
Let's set up some basic definitions to distinguish rendered from loaded:
rendered: React has converted your virtual DOM elements (specified in the render method) into real DOM elements and attached them to the DOM.
loaded: Image data or other remote content has downloaded completely (or failed to download).
So just add the onLoad and onError event handlers to your React <img> tag and away you go.
image-events
Short Example:
import React from 'react';
class ImageWithStatusText extends React.Component {
constructor(props) {
super(props);
this.state = { imageStatus: null };
}
handleImageLoaded(e){
this.setState({ imageStatus: 'loaded' });
console.log(e.target.scrollHeight);
}
handleImageErrored() {
this.setState({ imageStatus: 'failed to load' });
}
render() {
return (
<div>
<img
src={this.props.imageUrl}
onLoad={this.handleImageLoaded.bind(this)}
onError={this.handleImageErrored.bind(this)}
/>
{this.state.imageStatus}
</div>
);
}
}
export default ImageWithStatusText;

Categories