Reputation: 337
In a react app, I display 1-5 photos of each item. All the photos are in a folder under Node
, so the way I display them is using their url, i.e. http://10.1.1.1:3000/01-0001.JPG
So, I don't make an http request to get them.
Problem is:
It is not possible to know how many photos each item has. For example, one item could have 5 photos and another one only one photo. The format of the filename is this: 01-0001.JPG, 01-0001.1.JPG, 01-0001.2.JPG
So, what I currently do is this:
const Item1 = () => {
return (
<img src={`${process.env.REACT_APP_NODE_SERVER}/${item.No}.JPG`} onError={this.addDefaultSrc} alt='1'></img>
)
}
const Item2 = () => {
return (
<img src={`${process.env.REACT_APP_NODE_SERVER}/${item.No}.1.JPG`} onError={this.addDefaultSrc} alt='2'></img>
)
}
const Item3 = () => {
return (
<img src={`${process.env.REACT_APP_NODE_SERVER}/${item.No}.2.JPG`} onError={this.addDefaultSrc} alt='3'></img>
)
}
and I use this function to display a dummy photo when there is no photo
addDefaultSrc(ev) {
ev.target.src = `${process.env.REACT_APP_NODE_SERVER}/notfound.png`;
ev.target.onerror = null;
}
This is bad solution because I don't want the client to see a dummy picture, instead client should see only the true number of photos for each item.
I am using react-multi-carousel
<Carousel
responsive={responsive}
swipeable={false}
draggable={false}
showDots={true}
ssr={false} // means to render carousel on server-side.
infinite={false}
autoPlay={false}
autoPlaySpeed={1000}
keyBoardControl={true}
customTransition="all .5"
transitionDuration={500}
containerClass="carousel-container"
removeArrowOnDeviceType={["tablet", "mobile"]}
deviceType={this.props.deviceType}
dotListClass="custom-dot-list-style"
itemClass="carousel-item-padding-40-px">
<div><Item1 /></div>
<div><Item2 /></div>
<div><Item3 /></div>
</Carousel>
Any help would be much appreciated.
Upvotes: 1
Views: 557
Reputation: 11283
I think you should loop over images, set initial style to display: none
, wait for all callbacks and calculate appropriate state when everything is complete.
Example
const { useState, useEffect } = React;
const random = () => Math.floor(Math.random() * 5 + 5);
const getImages = () => Promise.resolve(Array(random()).fill(0).map((pr, index) => {
const id = index + 750;
return {
id,
url: `https://i.picsum.photos/id/${id}/100/100.jpg`,
loaded: false,
error: false,
alt: id
}
}))
const Images = ({onComplete}) => {
const [images, setImages] = useState([]);
const [allLoaded, setLoaded] = useState(false);
useEffect(() => {
let isUnmounted = false;
getImages()
.then(images => {
if(isUnmounted) {
return;
}
setImages(images);
})
return () => {
isUnmounted = true;
}
}, [])
useEffect(() => {
let isUnmounted = false;
const loadedImagesCount = images.reduce((acc, image) => acc + Number(image.error) + Number(image.loaded), 0);
if(images.length === loadedImagesCount) {
setTimeout(() => {
if(isUnmounted) {
return;
}
setLoaded(true);
onComplete();
}, 500);
}
return () => {
isUnmounted = true;
}
}, [images])
const onError = id => {
setImages(images => {
const imageIndex = images.findIndex(image => image.id ===id)
const image = images[imageIndex];
return [
...images.slice(0, imageIndex),
{
...image,
error: true
},
...images.slice(imageIndex + 1)
]
})
}
const onLoad = id => {
setImages(images => {
const imageIndex = images.findIndex(image => image.id ===id)
const image = images[imageIndex];
return [
...images.slice(0, imageIndex),
{
...image,
loaded: true
},
...images.slice(imageIndex + 1)
]
})
}
const loadedImagesCount = images.reduce((acc, image) => acc + Number(image.loaded), 0)
if(allLoaded && [0, 1].includes(loadedImagesCount)) {
return 'No images or only one image - hidden'
}
return <React.Fragment>
{images.map(({id, loaded, error, url, alt}) => <img key={id} src={url} className={`image ${error ? 'image--error' : null} ${allLoaded && loaded ? 'image--loaded' : null}`} onLoad={() => onLoad(id)} onError={() => onError(id)} alt={alt}/>)}
</React.Fragment>
}
const App = () => {
const [loading, setLoading] = useState(true);
const onComplete = () => {
setLoading(false);
}
return <div>
{loading ? 'Loading...' : null}
<Images onComplete={onComplete}/>
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.image {
display: none;
}
.image--error {
display: none;
}
.image--loaded {
display: inline;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>
Upvotes: 1