How should I implement lazy loading for my images in react? - reactjs

I'm trying to add lazy loading to my react application as I have over 200 images that I don't need on the initial load. If I lazy load the images, does this mean, they won't be loaded until they're needed on the screen?
I'm currently importing 4 groups of about 50 images into .js files and then adding them to objects so they can be dynamically used in my components. It looks like this...
// SportImages.js file
import Football from './images/football.png
import Cricket from './images/cricket.png
import Soccer from './images/soccer.png
... // another 50 or so imports
export default {
Football,
Cricket,
Soccer,
... // another 50 or so files
}
// and then in my component
cards.map(x => {
return (
<img src={SportImages[x.image_ref]} />
)
})
So my question is, should I be using lazy loading in the component file or in the main images file if I want to lazy load every image?

You can add the loading attribute to your image element to lazy-load the images. A complete explainer guide on this attribute can be found on web.dev.
In your case it would probably look as follows:
cards.map(card => (
<img src={SportImages[card.image_ref]} loading="lazy" />
))
You should also make sure to specify the width and height attributes so the browser can place the element on the page with the correct dimensions.

You can use this library called react-lazy-load-image-component
Import the LazyLoadImage component then use it the render you images
cards.map(card => (
<LazyLoadImage src={SportImages[card.image_ref]} {...props}/>
))
You can also add an effect when lazy loading the images. You can check the documentation of the library here
https://www.npmjs.com/package/react-lazy-load-image-component

loading='lazy' attribute Safari not supported
<img loading=lazy src="image.jpg" />
Try this package:
npm i react-lazy-load-image-component
Example:
import React from 'react';
import { LazyLoadImage, trackWindowScroll } from 'react-lazy-load-image-component';
const Gallery = ({ images, scrollPosition }) => (
<div>
{images.map((image) =>
<LazyLoadImage scrollPosition={scrollPosition}
width="100%"
height="auto"
src={image.url} />
)}
</div>
);
export default trackWindowScroll(Gallery);

Related

React is not dynamically loading images

I am trying to dynamically load images in a map function but it won't return anything.
I can get it to load a single image if I import it at the top of the page and hardcode the src but I have tried every solution I can find to do it dynamically and none of them work.
Here is what the code looks like. I am passing props with the title of the PNGs but nothing will load.
const Project = (props) => {
const proj = props.proj
return (
<div >
{proj.map(({title, id}) =>{
return(
<div>
<div className="..." key={id}>
<img
src={require(`../Assets/${title}.png`).default}
alt={`Image of ${title} hompage`}
className='object-cover'
/>
</div>
</div>
)
})}
</div>
)
}
export default Project;
The app was set up with create react app. I tried setting up a library but it didn't load the images either.

React file upload behaviour duplicated across components

I have created an image upload thingamajig called ImageSelector as a reusable component. It's for a custom CMS where the user first selects a header image, then selects several images for a gallery.
So the top part of the form uses the <ImageSelector /> component and the bottom part of the form uses an array of the same component. But for some reason whenever I select an image using the second ImageSelector at the bottom of the form, it only changes the image for the first ImageSelector.
It surely can't be the components' state that is bound together as I've never experienced that to be a problem, so it must be something to do with the way the browser caches files right? My question is, how can I make a reusable image upload component in React and avoid this duplication behaviour?
ImageSelector component:
(styles removed for brevity)
import { useState } from "react";
import { AiFillCamera } from "react-icons/ai";
import styled from "#emotion/styled";
export default function ImageSelector({ placeholder, shape }) {
const [currentImage, setCurrentImage] = useState();
const [currentImageUrl, setCurrentImageUrl] = useState();
function handleChangeImage(e) {
setCurrentImage(e.target.files[0]);
setCurrentImageUrl(URL.createObjectURL(e.target.files[0]));
}
return (
<ProfileImage $shape={shape}>
<div>
<ProfileImageOverlay $image={!!currentImage}>
<Label htmlFor="selector-image" $image={!!currentImage}>
{placeholder ? placeholder : <AiFillCamera />}
</Label>
<input
id="selector-image"
type="file"
name="selector-image"
onChange={handleChangeImage}
/>
</ProfileImageOverlay>
{currentImage && (
<img src={currentImageUrl} alt="selected image" width="170px" height="170px" />
)}
</div>
</ProfileImage>
);
}
Turns out this duplicate behaviour is what happens when you forget to change the html id attribute of subsequent instances of the input field!
Derp.

Create image slider with banner advertisement using react.js

I want to create an image slider in react js which works like a banner advertisement that has images and something is written in it. But have no idea how is it done. I have the following code
rrr.jsx
import React from 'react'
import img44 from '../../Assets/pic46.png'
import img45 from '../../Assets/pic47.png'
import img46 from '../../Assets/pic48.png'
const rrr = () => {
return (
<div>
<div className="banner" >
<img src={img44} alt="banner" />
<img src={img45} alt="banner" />
<img src={img46} alt="banner" />
</div>
</div>
)
}
export default rrr
Also, the picture for what I wanted is :
In this way, I would like to slide up to three pictures but don't have an idea how we do it. So, it would be good if someone would teach me.
For now, I have only the image and nothing written in it like . So, it would be good if someone would teach me how to make an image slider with banner advertisement.
With react you can use framer-motion
A production-ready motion library for React
In the docs It has a great example of building slide components uses AnimatePresence exit animations
I would like to slide up
The above example slide to the left, right. If you want to slide you can modify the variants from x to y.

Component Render Issues (Long Read)

I am dynamically getting data from my database to display on my website but I've encountered a problem and don't know how to fix it.
I have a main Home page which loads in a bunch of tiles that you can click on and take you to another page on my SPA. Some of the tiles you can click on can render STL objects from a file or some tiles will not when clicked on.
I have encountered an issue with my components that render STL files.
Issues?
Clickable Component is Multi Rendering my Model Component (or something like that?)
I have unmount and remount <Model/> for each page view?
General Issue with Project Setup?
Issue with STL (Model) component can be found at the bottom
Provided
Home.js (main component)
ClickableImage (component that returns [])
Layout.js (dynamic content pages)
Model.js (Component that loads my model)
Reproducible Code Current Issue CodeSandBox
Updates
11:03pm Feb 23 : Added in CodeSandBox
codesandbox code explanation. 2 Images are displayed, clicking on first image will take you to a layout that doesn't load in the STL file. The second image does. That's when everything breaks.
In Model.js under `/src/components/Layout/
Home.js
class Home extends Component {
render() {
const {projects} = this.props;
return (
<section className="max-container">
<div className="home-layout">
<div className="grid pl-4 pr-4">
<ClickableImage projects={projects}/>
</div>
</div>
</section>
);
}
}
ClickableImage
const ClickableImage = ({projects}) => {
const mappedData = projects && projects.map((project, index) => {
return (
<Link to={'/project3D/' + project.projectId} key={index}>
<img
src={project.banner}
alt="img" className="clickImage"/>
</Link>
);
})
return (
mappedData
);
}
Layout.js (/project3D/)
...
<div className="content-div">
<Canvas camera={{ position: [0, 10, 100] }}>
<Suspense fallback={null}>
<Model url={"./RaspberryPiCase.stl"} />
</Suspense>
<OrbitControls />
</Canvas>
</div>
...
So that is my general layout of everything on my page.
Home -> Click Image -> Layout Page.
Now this is where things get a little weird.
The Canvas portion on my layout page gives me this error when trying to load it.
Uncaught invalid array length react-reconciler.development.js:7648
The above error occurred in the <Model> component:
Model#http://localhost:3000/static/js/main.chunk.js:2430:15
Suspense
Canvas#http://localhost:3000/static/js/0.chunk.js:137042:66
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
But when I copy the Canvas section to my Home Component it will render my STL file with no problem and then it will also load my STL file on my other page as well.
Here are images to show you the problem without pasting <Canvas> in my home component.
PreHome Paste
After Pasting Canvas in Home Component
Model.js
import React, {useEffect, useRef} from "react";
import {STLLoader} from "three/examples/jsm/loaders/STLLoader";
import {useLoader, useThree} from "react-three-fiber";
export const Model = ({url}) => {
const geom = useLoader(STLLoader, url);
const ref = useRef();
const {camera} = useThree();
useEffect(() => {
camera.lookAt(ref.current.position);
}, [camera]);
return (
<>
<mesh ref={ref}>
<primitive object={geom} attach="geometry"/>
<meshStandardMaterial color={"orange"}/>
</mesh>
<ambientLight/>
<pointLight position={[10, 10, 10]}/>
</>
);
};
I just looked into it some more but apparently useLoader is what's most likely throwing my error, and I don't know why. (Only know this because when I comment it out the errors go away)
lmk if more info is required.
The problem is that useLoader() is having issues fetching the file you are providing in the url variable.
In your example, the value you end up providing as "url" is "./RaspberryPiCase.stl".
To fix your issue, simply provide the full absolute URL where your STL file can be fetched from.
Based on your provided Codesandbox, it's on your project /public folder, so a simple way to fix your issue is doing:
export const Model = ({url}) => {
const fullUrl = `${window.href.origin}${url.replace(".", "")}`;
const geom = useLoader(STLLoader, fullUrl);
// ...
See it live on a fork of your Codesandbox.
Additionally, you can follow this discussion in case your actual resource URL was not actually public (ie. required some kind of authentication). In that scenario, you can fetch the resource .gtl file first as an arrayBuffer, and then pass it to your loader.

Why is my react-lazyload component not working?

Up until a few days ago, my implementation of LazyLoad was working perfectly, but now I can't seem to get it to work.
This is my component:
import React from 'react';
import LazyLoad from 'react-lazyload';
import './image.scss';
const Image = image => (
<LazyLoad height={200} offset={100} once>
<div
className="image-container"
orientation={image.orientation}>
<img
className="image"
src={image.url}
alt={image.alt}
/>
{'caption' in image &&
<div className="meta">
<p className="caption">{image.caption}</p>
<p className="order">{image.currentNumber}/{image.maxNumber}</p>
</div>
}
</div>
</LazyLoad>
)
export default Image
And in App.js it is called like this:
render() {
return (
<div className="wrapper">
<GalleryTop details={this.state.gallery_top} />
{this.state.images.map((image, index) => <Image key={index} {...image} /> )}
</div>
)
}
But it won't work! Here's the demo environment:
https://gifted-kare-1c0eba.netlify.com/
(Check Network tab in Inspector to see that images are all requested from initial load)
There's also a video here
Any idea about what I am doing wrong?
Thanks in advance,
Morten
I ran into similar issues with the npm package react-lazyload. For one, this package only allows one child per LazyLoad component, so you would have to rearrange your hierarchy to make it work. Secondly, I found some strange behaviors when loading images that were already set within the viewport. The documentation does list import {forceCheck} from 'react-lazyload'; combined with forceCheck(); as a means of manually checking the elements, but I found this inconvenient and insufficient for components that aren't rerendering.
I was able to obtain the exact same functionalities with an easier implementation from the alternative package react-lazy-load. Mind the hyphen. This package also requires node>0.14. More or less, it does the same thing. You can read their documentation here: react-lazy-load

Resources