OnLoad doesn't fire on cached images - javascript

I'd like to listen to the load event of my images. And here's how I'm trying to.
export const Picture: FC<PictureProps> = ({ src, imgCls, picCls, lazy, alt: initialAlt, onLoad, onClick, style }) => {
const alt = useMemo(() => initialAlt || generator.next().value, [src, initialAlt]);
const { publicRuntimeConfig } = getConfig();
return (
<picture style={style} className={picCls}>
{!publicRuntimeConfig.dev && <source srcSet={`${src}.webp`} type="image/webp"/>}
<img onLoad={onLoad} className={imgCls} alt={alt} src={src} loading={lazy ? 'lazy' : 'eager'}
onClick={onClick} />
</picture>
);
};
As I understand, this code should work fine. But when images are cached, they don't trigger load event from time to time.
I need to know, when all my images are loaded. To do this I checked the amount of loaded images. But it doesn't work when at least 1 of images didn't trigger the event. error event doesn't fire as well, and I can see that all of images are always loaded in the debugger tools.
What should I do?
I know I can add something like src?_dc=timestamp, but I do need caching, it really speeds up re-upload.
UPD: All of the loaded images returns 304 status. But the amount of images, that triggers load differs from 0 to total amount

Okay, this is not exactly what I asked for, but I found one thing that can help me - the completed field
export const Picture: FC<PictureProps> = ({ src, imgCls, picCls, lazy, alt: initialAlt, onLoad, onClick, style }) => {
const alt = useMemo(() => initialAlt || generator.next().value, [src, initialAlt]);
const ref = useRef<HTMLImageElement>();
const onLoadTriggered = useRef(false);
const { publicRuntimeConfig } = getConfig();
const onLoadFunc = useCallback(() => {
!onLoadTriggered.current && onLoad?.();
onLoadTriggered.current = true;
}, [onLoad]);
useEffect(() => {
ref.current.complete && onLoadFunc();
}, []);
return (
<picture style={style} className={picCls}>
{!publicRuntimeConfig.dev && <source srcSet={`${src}.webp`} type="image/webp"/>}
<img onLoad={onLoadFunc} className={imgCls} alt={alt} src={src} loading={lazy ? 'lazy' : 'eager'}
ref={ref} onClick={onClick} />
</picture>
);
};
This code did solve my problems, but it seems like it's a crutch. So, I've decided to leave this solution here, but I'm still waiting for the correct answer

this issue occurs because always it fetch images from cache
please add timestamp after image URL
you can do it from both side
From Backend (DB side)
for example :
#ImagePath+ REPLACE(ImagePath, '~', '') + '?time='+FORMAT(getdate(),'yyyyMMddHHmmssffff')
in IONIC code (Typescript)
for example :
npm i moment --save
import * as moment from moment
const dateStringThatWorksForIonicDatepicker = moment.unix(1536883200000).format(‘YYYY-MM-DD’)

Related

prevent image drop duplication while using react dropzone?

I'm using dropzone in my Next JS app. and i'm facing an issues. one of them is i can't prevent image duplication correctly.
what is did is i created a useState() to check if element exists.
const [found, setFound] = useState(false);
and on onDrop() method i checked if the dropped file exists in the stored files. (i haven't figured out to check duplication on multiple image drag). it works but not correctly.
const [files, setFiles] = useState([]);
const [found, setFound] = useState(false);
const { getRootProps, getInputProps } = useDropzone({
accept: {
"image/*": [],
},
onDrop: (acceptedFiles) => {
acceptedFiles.filter(
(el, idx) =>
idx ===
files.findIndex((elm) => elm.name === el.name && setFound(true))
);
if (!found) {
setFiles((prev) => [
...prev,
...acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
),
]);
}
},
});
the issue with my code is that if i insert images and drag another duplicated image it stores that. but on the next drag it doesn't add the image, whether it is duplicated or not. if it's duplicated again then the next drag won't work either and the first drag after the duplicated drag doesn't work too. but the next non duplicate drag will work.
i think this is happening b/c the condition is checked after the drag is completed and file is added.
how can i drag and drop images correctly by not passing duplicated images in single or multiple drags?
if it's not much to ask can anyone show me to make a limitations to the images that are being dragged or inserted? limit is between 4 and 9.
Thank You in Advance

React img onError not firing

I am trying to use a third-party API for getting YouTube thumbnails with higher resolution, which sometimes fails with code 404. If I fail to fetch the image, I would like to replace the src with the default YouTube thumbnail retrieved using its own API (I have the url stored in a json). When I tried to implement this using the img onError event, it appears that nothing is fired when the fetch fails. Does anybody know what's going on? Thanks!
const TestView = () => {
const videoId = "tuZty35Fk7M"
return (
<div>
<img src={`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`} onError={() => {console.log("errored")}}></img>
</div>
)
}
Update:
This ended up working pretty well
const TestView = () => {
const videoId = "tuZty35Fk7M"
const imgRef = useRef(null)
const [thumbnailSrc, setThumbnailSrc] = useState(`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`)
const defaultThumbnailSrc = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
const handleImgValidity = () => {
if (imgRef.current.naturalHeight == 90) {
setThumbnailSrc(defaultThumbnailSrc)
}
}
return (
<div>
<img ref={imgRef} src={thumbnailSrc} onLoad={handleImgValidity}></img>
</div>
)
}
It's nothing to do with React. Try visiting that url in your browser with a random string for the videoId. It will still display an image - in this case the default youtube thumbnail. So even though it's technically a 404, and your browser will report it as such, the image still has something to display so it won't fire the onError function.
If you replace the url in your code with something that is definitely a 404, your code works fine. Run the snippet below where i've swapped out youtube for google and you'll see your error message.
function onError() {
console.log('errored')
}
<img src="https://img.google.com/vi/some-random-id/maxresdefault.jpg" onError="onError()" />
Here is the working example of firing onError function.
https://codesandbox.io/s/reactjs-onerror-f7dq77

Gatsby\React - window.location.href not working

I need help with my code. The thing I want create is to change className according to page url
So when I scroll or go to page /kontakt I want to change class from "hamburger" to "hamburger active".
I also tried regex. Any ideas?
Here is code:
const HamMenu = ()=> {
const [sidebar, setSidebar] = useState(false)
const [burger, setBurger] = useState(false)
const url = window.location.href;
const showSidebar = () => setSidebar(!sidebar)
const changeColor = () => {
if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
setBurger(true);
} else {
setBurger(false);
}
}
window.addEventListener('scroll', changeColor);
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
onClick={showSidebar}
/>
</Link>
</div>
Dealing with window in Gatsby could be a little bit tricky because two fundamental reasons:
window object is only defined in the browser, so it will work perfectly under gatsby develop but you will need to add a "hack" to avoid a code-breaking in the gatsby build (because there's no window in the Node server).
Treating the window outside React ecosystem, may break the rehydration of the components. This means that React won't potentially know what components need to re-render on-demand, causing unmounted components, especially when navigating forward and backward using the browser's history.
There are a few workarounds to achieve what you're trying to do.
Gatsby, by default, provides a location prop in all top-level components (pages). So you can pass it to any child components at any time to change the class name based on its value:
const IndexPage = ({ location }) =>{
return <Layout>
<HamMenu location={location} />
<h1> some content</h1>
</Layout>
}
Then, in your <HamMenu> component:
const HamMenu = ({ location })=> {
const [sidebar, setSidebar] = useState(false)
const [burger, setBurger] = useState(false)
const url = window.location.href;
const showSidebar = () => setSidebar(!sidebar)
const changeColor = () => {
if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
setBurger(true);
} else {
setBurger(false);
}
}
useEffect(() => {
if(typeof window !== "undefined"){
const url = window.location.href
const changeColor = () => {
setBurger(window.scrollY >= 60 || url.contains("kontakt"))
}
window.addEventListener('scroll', changeColor)
return () => {
window.removeEventListener('scroll', changeColor)
}
}
}, [])
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars' location.pathname.includes("your-page")? ''some-class' : 'some-other-class' } >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
onClick={showSidebar}
/>
</Link>
</div>
I would suggest another approach to get the scroll position rather than using directly the window, using React-based approach to avoid what I was pointing before (How to add a scroll event to a header in Gatsby).
However, I've fixed your initial approach, wrapping it inside a useEffect with empty deps ([]). This function will be triggered once the DOM tree is loaded, to avoid the code-breaking window use that I was talking about. Alternatively to url.indexOf("kontakt") > -1 you may want to use url.includes("kontakt") which is way more readable.
Regarding the rest, it's quite self-explanatory. Destructuring the location props you get access to a bunch of data, the pathname property holds the page name so based on that, you can add a ternary condition wherever you want, such as location.pathname.includes("your-page") ? ''some-class' : 'some-other-class' (includes is more semantic in my opinion).
As you see, I've fixed your approach but I've also added a React/Gatsby-based one, choose what makes you feel comfortable.
React components rendering server-side (such as during gatsby build) do not have access to window, and in order to avoid breaking hydration, the first render needs to match what is rendered server-side. For these reasons, you'll want to use useEffect to make client-side changes that rely on window after the component mounts.
Note that this solution is going to perform rather poorly since changeColor is calling setBurger on each scroll event, which prompts the component to be re-rendered (even if the value is the same). You'll want to add a debounce or throttle routine to mitigate this.
const HamMenu = ()=> {
const [burger, setBurger] = useState(false)
useEffect(() => {
const url = window.location.href
const changeColor = () => {
setBurger(window.scrollY >= 60 || url.contains("kontakt"))
}
window.addEventListener('scroll', changeColor)
return () => {
window.removeEventListener('scroll', changeColor)
}
}, [])
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
/>
</Link>
</div>
</StyledMenu>
)
}

How to scroll 'onClick' component that is hidden before onClick event

When I click on the image I want that it is automatically scrolled to the component which is then displayed. I tried with anchor tags, but it's not working (I believe due to the fact that the component is hidden and at the same time when it is shown it should be scrolled to it ) , useRef - I get the error 'not defined' (I believe same reason as above).
Component is displyed onClick, but it does't scroll to the view-port of the user. Pls help, I'm out of the ideas :/
const WebContent = () => {
const [hidden, setHidden] = useState(false)
return (
<div>
<img onClick={() => setHidden(true)} src={first}/>
<div>
{hidden && <MyComponent/>}
</div>
</div>
)}
Your intuition is probably right that MyComponent is not yet mounted when you try to scroll to it. A simple way to do this would be to have MyComponent scroll itself into view when it mounts, if that's the behavior you're looking for.
const MyComponent = () => {
const ref = React.useRef(null);
useEffect(() => {
if (ref.current) ref.current.scrollIntoView();
}, [ref]);
return (
<div ref={ref}>
NOW YOU SEE ME
</div>
);
};
export default MyComponent;
One (hacky?) idea is add the ref to the surrounding div of the hidden content:
this.scrollHere = React.useRef(null);
...
return (
<div style={{ minHeight: 1 }} ref={this.scrollHere}>
{hidden && <div>My Hidden Component</div>}
</div>
)
Then you can run a function onClick, which sets hidden to true (which by the way is kinda irritating. Maybe just use "shown" as a quick improvement) and also lets the ref scrollIntoView:
const showAndScroll = () => {
setHidden(true);
this.scrollHere.current.scrollIntoView({
behavior: "smooth"
});
};
The minHeight has to be placed on the div since it is at height of 0 first and this messes with the scroll function (it scrolls below the hidden content).
See working example here.

Lazy load images (webp, jpg) with picture, source and img tags (React)

I'm trying to implement a lazy-loading image component with React currently.
My original solution was a very simple and basic approach. I used Image API and listened to onload event.
There is an example of React hook that was handling this functionality:
function useImage(src) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!loaded) {
loadImage();
}
function loadImage() {
const img = new Image();
img.onload = () => setLoaded(true);
img.src = src;
}
});
return loaded;
}
and code of the React component looked as follows:
function LazyImage({ src, ...props }) {
const isLoaded = useImage(src);
return (
<div className="container">
{isLoaded ? (
<img src={src} {...props} />
) : (
<div className="preloader" />
)}
</div>
);
}
Now I want to get the benefit of webp format which is supported by the API where I load the images.
Though browser support of webp format is not that good, it's possible to provide images inside a <source /> tag and browser will decide which image to load, also <img /> will be used as a fallback.
My React component was updated accordingly:
function LazyImage({ src, ...props }) {
const webpSrc = getWebpSrc(src);
const isLoaded = useImage(src);
return (
<div className="container">
{isLoaded ? (
<picture>
<source srcset={webpSrc} type="image/webp" />
<source srcset={src} type="image/jpeg" />
<img src={src} {...props} />
</picture>
) : (
<div className="preloader" />
)}
</div>
);
}
The problem here is that I'm still listening to the original jpg src to load in order to display the image. Unfortunately I couldn't just switch to listening to webp src as in browsers like Safari it will fail to load...
Possibly I can try to load both resources (webp and jpg) in parallel and update the state when the first one resolves. However this feels like making more requests instead of optimizing performance actually.
What do you think would be a good approach here? Or am I going in a wrong direction? Thanks!
You can use the IntersectionObserver Api.
First load an placeholder, when the element is intersected you can switch your placeholder with your desired src, this way you can only render the image if the element is in view
Intersection Observer API

Categories

Resources