I am trying to build an image component in ReactJS with a fallback prop if provided will render the fallback component until the image has been downloaded and rendered properly.
The conditional rendering is as follows :
if (loading) {
return fallback;
} else if (error) {
return <span>ERROR</span>;
} else if (src) {
return (
<>
<img alt="" ref={imageRef} decoding="async" src={props.src} />
</>
);
} else return null;
}
And the Image is called in the App.js file as
<Image
src="https://source.unsplash.com/random/100x100"
fallback={<h1>Loading...</h1>}
/>
I'm using useEffect hook to call function that further checks if the image has been mounted to the screen using ref.
React.useLayoutEffect(() => {
imageRef.current = true;
start();
return () => {
imageRef.current = false;
};
}, []);
async function start() {
if (!src || !fallback) {
const errorMessage = "`src` & `fallback` must be provided";
setError(errorMessage);
return;
}
setLoading(true);
tryLoadImage();
}
async function loadImage() {
const img = imageRef.current;
if (!img) {
return;
}
setLoading(false);
}
async function tryLoadImage() {
try {
await loadImage();
if (imageRef.current) {
setLoading(false);
}
} catch (error) {
console.log("error", error);
}
}
The problem I'm facing:
return part, the component gives a black-white space then renders the image, hence no such 'Loading...' is getting rendered on the screen.Things I'm unsure about:
Kindly refer to Codesandbox link for the live demo/code : https://codesandbox.io/s/img-lazy-ke6zz
Any help in the right direction would be quite helpful.
You can use useEffect better in this case; you have a state loading. You should listen to when this value changes in your useEffect.
useEffect(() => {
setLoading(false);
}, [loading]);
The value you pass in the array in the useEffect hook is getting listened to (there can be more than value in here, as it's an array). So when one of those values changes you run the code in useEffect. In this component loading should only changes once to false. So whenever you use setLoading in your code this useEffect is ran.
I recommend you read more about this hook here: https://reactjs.org/docs/hooks-effect.html
Also, I think it's better in this case, you default the loading state to true instead of false. So when the component renders it is loading from the start. In the start function you can use setLoading to false when src or fallback isn't set. This is also why your fallback isn't being shown. As you default loading to false and you show the fallback when this is loading is true.
A better practice of using React.useEffect and React.useState is importing it in your file. This can be done as followed import React, { useRef, useEffect, useState } from "react";. Then you can use them like this const [loading, setLoading] = useState(false); without React prefixing it.
You can find an edited version of your CodeSandbox here https://codesandbox.io/s/img-lazy-forked-l8zuf?file=/src/Image.js. I also changed the returning statement, which you can do if you don't want to show errors.