Rendering image dynamically is so hard in React - javascript

After trying various ways for hours and checking every relatable link, I couldn't find any proper way to render image dynamically in React.
Here is what i am trying to do.
I have an array of objects in which each object has a attribute called name. I am using map function map to loop over this array and returning of array of img element like shown below.
<img className="img-thumbnail" src={require('../../public/images/'+item.name+'.png')}/>
where item.name is the name of image file I want to display, for which require is giving me error "cannot find module".
Moreover I need to implement some fallback option, where rather showing broken images incase image file does not exist, i want to display default image
Here are the things I have tried:
using try and catch block over require and calling this function from img element
setImage(data){
try{
return require( '../../public/images/'+data+'.png' ) //actual image
}catch(err){
console.log(err);
return require('../../public/images/fallback.png'); //fallback
}
<img className="img-thumbnail" src={this.setImage(item)}/>
using import, inside same function above, got error import cannot be called from inside of function
using react-image library. Turned out it does not support local images.
Any help ?

Here a tricky way to handle this. Use react state to check if there's error.
If true, show fallback, otherwise, show actual image.
setImage = (data) => {
const image = new Image();
image.src = '../../public/images/'+data+'.png';
this.setState({
hasError: false
})
image.onerror = () => {
this.setState({
hasError: true
})
}
return image.src;
}
// into render
this.state.hasError
? <img src="../../public/images/fallback.png" />
: <img className="img-thumbnail" src={this.setImage(item)}/>
Update: Example
var image = new Image();
image.src = 'fake.jpg';
image.onerror = () => {
console.log('image doesn t exist');
}

I dont know why you need required it could be done simply like this. You can import something like this. Import image like this
import fallback from '../../public/images/fallback.png';
and for dynamic image i would suggest either make some key value pair. For ex :
let data = {
image1 : ../../public/images/image1.png,
image2 : ../../public/images/image1.png
}
and import it normal
and something in render
it could be something like this.
render(){
return(
<img className="img-thumbnail" src={img?img[type]:fallback}/> //something its just refrence(where type is something which you want dynamically add image)
)
}

Requires are statically checked during compile time. The path of requires cannot be dynamic. Since you have static images in your bundle and the object maps to one of these you can follow a solution to something as follows
const images = {
image1: require('local/path/to/image1'),
image2: require('local/path/to/image2'),
image3: require('local/path/to/image3'),
}
const defaultImage = require('local/path/to/defaultImage');
const Img = ({ name }) => {
// here name is the name for the image you get from api..
// This should match with the keys listed the iages object
return <img src={images[name] ? images[name] : defaultImage}/>
}

Above all answers were helpful but unforutnaley none of the method worked for me. So again digging little deep I found that require was giving error "cannot find module" because after webpack bundles my code, require lost the context. Which means the given relative path was no longer valid.
What i needed to do was preserve context which I did by using require.context;
Here is the final code that worked.
//getting the context of image folder
const imageFolderContext = require.context('realtive/path/to/image/folder')
//function to check if image exist
checkFile(data){
try{
let pathToImage = './path/to/image/relative/to/image/folder/context
imageFolderContext(pathToImage) //will check if Image exist
return true //return true if exist
}catch(err){return false}
}
//rendering image element dynamically based on item name and if exist or not
<img src={this.checkFile(itemName)?imageFolderContext('path/to/image/relative/to/context'):imageFolderContext('default/image/path/) />
don't forget to bind checkFile function

Related

Dynamically Updating Image SRC using ES6

I have global JavaScript variable that is defined in PHP for use in a WordPress Block.
When the page initially loads, the "stars" image appears as expected. When a change event is fired, a 404 error appears in the console with "https://site.dev/img/var_set_in_php.imageSunset" as the URL instead of "https://site.dev/img/sunset.jpg".
How can this be updated to pull in the value that is set in PHP? I'm new to ES6, so hoping it's just something simple that I'm missing.
wp_localize_script(
'image-js',
'var_set_in_php', // Array containing dynamic data for a JS Global.
array(
'imageStars' => '/img/stars.jpg',
'imageSunset' => '/img/sunset.jpg',
)
);
function onChangeFunction(name) {
// name is set to "Sunset"
document.getElementById('image').src = `var_set_in_php.image${name}`;
}
<img src={var_set_in_php.imageStars} id="image" />
I think you have a
var var_set_in_php = {
'imageStars' :'/img/stars.jpg',
'imageSunset' : '/img/sunset.jpg'
}
and if you do, you need this to access the object with a compound variable
document.getElementById('image').src = var_set_in_php[`image${name}`];

Find images in the above fold using Puppeteer

I'm trying to find images in the above fold using Puppeteer and set attribute loading="eager" to those images.
Here is what I've tried:
const images = await page.$$("img");
images.forEach(async image => {
if (await image.isIntersectingViewport()) {
console.log("intersecting");
image.setAttribute("loading", "eager");
}
});
It finds the images in the above fold correctly. However, when I try to set the attribute to it, it throws an error: TypeError: image.setAttribute is not a function
image is an ElementHandle, that means that it's an object on the puppeteer's world pointing to an element in the browser world.
If you want to set an attribute to the DOM element, you could call evaluate and set that in the browser.
await image.evaluate(i => i.setAttribute("loading", "eager"));

Dynamically loading assets with require() that might not exist in webpack + VueJS

I'm trying to load images dynamically which may or may not exist.
In this case, crypto-currency icons using their 3 letter symbol. I have a few hundred in .svg format in my statics library and when I pull data from a price server I try to match up the icons I have with the symbols coming from the server and to serve a fallback image if I don't have the asset.
In my index.vue I can get away with this code and everything works fine:
<img :src="'statics/icons/svg/' + coin.symbol + '.svg'" v-img-fallback="'statics/icons/svg/fallback.svg'"/>
However in a subcomponent that opens if a user clicks a coin the same code will fail to load both the primary and fallback images. I've tried numerous ways but the only way to get an image to load from my subcomponent is to either hard code it like this:
<img src="statics/icons/svg/btc.svg"/>
Which is impossible for me as I need the modal to be dynamically generated for any possible coin...
Or using require() like this:
<img :src="imageSrc" v-img-fallback="require('../statics/icons/svg/fallback.svg')"/>
// Computed:
imageSrc () {
if (this.coinData.symbol) {
return require('../statics/icons/svg/' + this.coinData.symbol + '.svg')
}
}
However this crashes my app if require() looks for an asset that doesn't exist. I need a method that fails gracefully so that the v-img-fallback can detect it and supply the fallback.
I've tried doing something like return require(image1) || require(fallback) but it doesn't work.
This is a common request and latest WebPack, AFAIK (and I just searched for it again), does not expose an API for especifically testing the existence of a module.
In other words, you'd have to handle the uncertainty of the loading yourself. Example:
computed: {
imageSrc () {
if (this.coinData.symbol) {
try {
return require('../statics/icons/svg/' + this.coinData.symbol + '.svg')
} catch (e) {
if (e.name !== "ModuleNotFoundError") throw e; // handle false-positives
// in cordova, use the line below instead of the above
// if (!e.message.startsWith('Cannot find module')) throw e;
return require('../statics/icons/svg/fallback.svg');
}
}
return require('../statics/icons/svg/fallback.svg');
}
}
This way I'd argue you wouldn't even need a fallback src in the template. You could return it in the computed property itself.

preload images on javascript (React, Jquery) app, images loaded twice

Please look at EDIT 3 below, as I figured out that it is not an issue with React but with the browser's chaching mechanism.
I'm trying to create an app that creates some simple carousel out of given array of images url. I've written a module that helps me to invoke a callback once all the images are loaded, this is how it look:
ImagesLoader.js
export default {
// a module that loads an array of images urls and when completes
// calls a callback with the images elements array
load(imagesUrlsArray, callback){
const imagesLoaded = [];
imagesUrlsArray.map((url) => {
const img = new Image();
img.onload = () => {
imagesLoaded.push(img);
if(imagesUrlsArray.length === imagesLoaded.length){
callback(imagesLoaded);
}
};
img.src = url;
});
}
}
I know I can use promises and resolve once all urls are loaded, and no check for errors right now.
Here is where my component updates the state with the images retriveed from the module above:
componentDidMount() {
ImagesLoader.load(this.props.images, (loadedImages) => {
const imagesStateData = this.calculateImagesStateData(loadedImages);
this.setState(Object.assign({},
imagesStateData,
{
loaded: true,
loadedImages: loadedImages
}
));
});
}
What happens is when I click the next or previous button (look at the screenshot below) each time the image loaded again, I can't understand why.
this is the first time I do such thing with reactjs, with jquery had no problems
Here is how I pass the images urls:
export default class App extends Component {
render() {
var images = [
"http://www.wallpapereast.com/static/images/excellent-love-quotes-wallpaper-hd.jpg",
"http://www.wallpapereast.com/static/images/My-Samsung-Galaxy-S3-Wallpaper-HD-Landscapes1.jpg",
"https://s-media-cache-ak0.pinimg.com/236x/58/01/02/5801020fea36221ffba33633f99a7d81.jpg",
"http://www.wallpapereast.com/static/images/pier_1080.jpg",
"http://www.wallpapereast.com/static/images/wallpaper-black-hd-hd-wallpapers.jpg",
"http://1.bp.blogspot.com/-dvg12YJKaKg/UnVfkMke7jI/AAAAAAAAUaU/O86x5FMgEuk/s1600/longcat.gif"
];
var settings = {
autoPlay: true,
arrows: true
};
return (
<Layout>
<ReactCarousel images={images} width={500} height={300} settings={settings}/>
</Layout>
);
}
}
EDIT:
managed to give a short example. http://codepen.io/anon/pen/ObyRPX , what I could see from building this example is if I don't use the ImageItem component and render a simple <img src='image.src'/> element it works good, so I guess my problem is with the ImageItem component.
EDIT 2:
this is pretty weird http://codepen.io/anon/pen/eBpdjx here I just changed it so ImageItem renderes image element rather than background image on a div and it works as expected. Can anyone explain what is the difference?
EDIT 3:
Apparently this is happening not only on react apps but on jquery app aswell, have a look here:
http://codepen.io/anon/pen/VmvyPZ
Again when I try to load the image as background-image property of the css, the browser doesn't cache the image and load it twice.
I faced the exact same issue. The image gets loaded again because the browser is not holding a reference to the image that you have loaded.
All I did was create a global array and push all the images to the array.
Now, this array is global and the browser has to maintain the array until the whole app gets destroyed.
var allImages = [];
function loadImages(imageUrls) {
imageUrls.forEach((q) => {
const img = new Image();
img.src = q;
allImages.push(img);
});}
After this, I was able to load the images on other pages, even when offline.
Chrome shows 2 image requests in devtool for one image. Try to uncheck disable cache option in the network tab.

How can you dynamically change the source in a require statement that requires an image file?

The image I'm using in React Native uses a require statement for the source of the image. I want to change the source of the image so it is kind of of like switching out frames in an animation.
var module1 = './hello';
var module2 = './goodbye';
state = {
module: module1 // will later be changed to module2
}
require(this.state.module);
Require both and then use the correct one where you need it:
var hello = require('./hello');
var goodbye = require('./goodbye');
hello.myFunction();
// later:
goodbye.anotherFunction()
I dont think it is a good programming style but it is possible.
For example the modules look like this
exports.name = () => {
console.log('modul1')
}
And the main file like this, it will work like expected
const modul1 = './modul1'
const modul2 = './modul2'
var modul = require(modul1)
modul.name() // -> modul1
modul = require(modul2)
modul.name() // -> modul2
I ended up just creating an object like this:
var frames = {
'1': require('./assets/phase1.png'),
'2': require('./assets/phase2.png'),
'3': require('./assets/phase3.png'),
'4': require('./assets/phase4.png'),
}
and in my Image tag, I set the source attribute to this.state.frame and setState with a new key from frames like frames[imageIndex]. Luckily I didn't have too many images, but this isn't the most ideal solution.
EDIT:
Another solution (which is more elegant and less verbose) is to create a drawable folder in android>app>src>main>res and drop all the images in the that folder. On iOS, you can drop all your images in ios>Appname>Images.xcassets.
From there, you can set the source of your images to {uri: this.state.imageFile} where this.state.imageFile is a string without the file extension and you can change them dynamically without having to require() each file.

Categories

Resources