How to show an image when Chrome is offline - javascript

I'm trying to show an image when the Chrome browser is offline, and when it's online show the webpage.
I transferred the image to base64 data and tried to load it in the img tag, however the base64 data is too large.
Is there a way to show an image when the browser is offline?
import imageToBase64 from "image-to-base64";
const Home = () => {
const [isOnline, setIsOnline] = useState(true);
// Checks to see if the browser has internet connection or not
window.addEventListener("online", () => setIsOnline(true));
window.addEventListener("offline", () => setIsOnline(false));
//Link to the image
const idleImgUrl = `${window.location.href}${coffeeMachine}`;
//convert image to base64 and save to local storage
imageToBase64(idleImgUrl)
.then(res => {
window.localStorage.setItem("idleImgData", res);
})
.catch(err => {
console.log(err);
});
return (
isOnline
? (<div>The web page to show</div>)
:
// <p> tag shows
<p>The browser is offline now</p>
// img tag does not show
(<img src={window.localStorage.getItem("idleImgData"} />)
);
};
Any help would be appreciated...

The trick is to load the image while the user agent still has an internet connection. The image won't be downloaded until you render the <img> tag. The cached image can then be displayed without issue later.
I wrote a short create-react-app example to illustrate.
import React, { useState, useEffect, useCallback } from 'react';
const App = () => {
const [online, setOnline] = useState(true);
const onlineListener = useCallback(() => setOnline(true), [setOnline]);
const offlineListener = useCallback(() => setOnline(false), [setOnline]);
useEffect(() => {
window.addEventListener('online', onlineListener);
window.addEventListener('offline', offlineListener);
return () => {
window.removeEventListener('online', onlineListener);
window.removeEventListener('offline', offlineListener);
};
}, [onlineListener, offlineListener]);
return (
<div className="App">
<img
style={online ? { display: 'none' } : undefined}
src="TODO"
alt="no internet"
/>
</div>
);
};
export default App;
It displays an image when the user agent loses connection and hides it again when connection is restored. It obviously won't work if connection is cut to begin with, but in such a case how did the user load your application 🤔?

If you are offline, you might not even be able to load your react bundle.js file in the first place and there is nothing you can do about it.
Also, I don't see the advantage of keeping it in your localStorage in this case. Browsers are probably going to cache it anyway, if size matters here.
If the user was able to load your bundle, you can just store the b64 hardcoded as a variable directly in your bundle or initiate an ajax on componentDidMount (using useEffect since you use hooks).
const Home = () => {
const [base64Img, setBase64Img] = useState();
const [isOnline, setIsOnline] = useState(true);
const setOnline = () => setIsOnline(true);
const setOffline = () => setIsOnline(false);
useEffect(() => {
initiateB64Retrieval().then((b64) => setBase64Img(b64));
window.addEventListener('online', setOnline);
window.addEventListener('offline', setOffline);
return () => {
window.removeEventListener('online', setOnline);
window.removeEventListener('offline', setOffline);
}
}, [])
...
}
Always good practice to remove your event listeners. Note that you cannot remove event listeners if passed with anonymous functions or when using .bind() as it creates another reference for the function.

Related

Having an issue with making react component work inside a custom iframe component

So, I've basically tried everything with this one. I ran out of solutions or options. Thing is, I have a button. When you click on it your camera will open and you will see some filters that you can apply to your face. I am new to React. Made it work without the iframe to test the API first, but it's not working anymore inside this iframe. The react component needs to be inside this iframe. The code can be found here with what I did so far/tried: https://codesandbox.io/s/cool-fog-3k5si5?file=/src/components/button/button.jsx
The problem is that when I click the button, the canvas disappears from the page and I get this error in the console:
The DeepAR API fails initialization because the canvas is no longer on the page and it crashes. I really don't know what to search for as I considered this to be a react render error and I tried different ways to write the react code (functional/class). If you have any ideas or suggestions, please help. Thank you in advance.
Your use of useEffect in your Modal and App Component is incorrect.
To remind you, useEffect accepts a function which runs after the render is committed to the screen.
If the function returns a function (which is your case), this function is the "clean up" function which is run before the component is removed from the UI.
So what is happening is that your useEffect code is run only when your components are being unmounted.
Since we are not concerned with any clean up at this stage, a quick solution for you is to move the clean up expressions to the main effect function as follows:
useEffect(() => {
fetch(
"https://cors-anywhere.herokuapp.com/https://staging1.farmec.ro/rest/V1/farmec/deeparProducts/"
)
.then((response) => response.json())
.then((productsJson) => setProducts(productsJson));
}, []);
The same goes for your Modal component :
useEffect(() => {
let initializedDeepAR = new DeepAR({
licenseKey:
"6fda241c565744899d3ea574dc08a18ce3860d219aeb6de4b2d23437d7b6dcfcd79941dffe0e57f0",
libPath: DeepAR,
deeparWasmPath: deeparWasm,
canvas: canvas.current,
segmentationConfig: {
modelPath: segmentationMode
},
callbacks: {
onInitialize: () => {
// let filterName = colors[0].filterData[0]['Filter Binary Path'].match(new RegExp("[^/]+(?=\\.[^/.]*$)"))[0];
setDeepAR(initializedDeepAR);
initializedDeepAR.startVideo(true);
// initializedDeepAR.switchEffect(0, 'slot', `https://staging1.farmec.ro/media/deepArFilters/${filterName}.bin`);
}
}
});
/*#TODO: replace paths with server local path*/
initializedDeepAR.downloadFaceTrackingModel(models);
}, []);
With one additional fix concerning your use of useRef.
To target the element behind the useRef, you must use the .current property.
Finally, your Frame component is using useState to manage the mounting of the iframe. I would suggest using the useRef hook with a useState for your mountNode as follows:
export const Frame = ({
children,
styleSelector,
title,
...props
}) => {
const contentRef = useRef(null)
const [mountNode, setMountNode] = useState()
useEffect(() => {
setMountNode(contentRef.current.contentWindow.document.body)
}, [])
useEffect(() => {
const win = contentRef.current.contentWindow
const linkEls = win.parent.document.querySelectorAll(
styleSelector
)
if (linkEls.length) {
linkEls.forEach((el) => {
win.document.head.appendChild(el)
})
}
}, [styleSelector])
return (
<iframe title={title} {...props} ref={contentRef}>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}

how to stop re-rendering every time a state changes in React js

im fetching images from firebase Storage but every time any state changes here is the code :
const getImage = (name) => getDownloadURL(ref(storage, name));
function PostImage({ image, alt }) {
const [imageSrc, setImageSrc] = useState();
useEffect(() => {
(async () => {
setImageSrc(await getImage(image));
})();
}, [image]);
return imageSrc ? (
<img src={imageSrc} className="sm:w-1/2 w-full rounded-lg" alt={alt} />
) : null;
}
I can't wrap it in a useEffect since it will only limit its scope so a cant call the function out side the useEffect, what can I do to only render the image on page load?
any help is appreciated , Thanks
Let me correct you - the code below says this :
execute this effect every time the value of image changes.
useEffect(() => {
(async () => {
setImageSrc(await getImage(image));
})();
}, [image]);
then in the effect you're updating the value of image. Hence the value of image will change. Since the value of image is changed , the effect will be executed again and the cycle will continue till infinity.
This is the anti-pattern.
If you want to fetch the images only once then :
useEffect(() => {
(async () => {
setImageSrc(await getImage(image));
})();
}, []);
Read more about useEffect

Change React Context without triggering a re-render

I have a download dialog that you can click on to download the graphics on my website. Because the graphics are created with HTML Canvas, I cannot simply download an array of image links. I have to contact each of the components and render each of them into a data URI.
On my app component, I have defined a context to keep track of which components should be downloaded.
export const DownloadFlagsContext = React.createContext<any>(null)
const App: React.FunctionComponent = (props) => {
const [downloadFlags, setDownloadFlags] = useState([])
return (
<DownloadFlagsContext.Provider value={{downloadFlags, setDownloadFlags}}>
<DownloadDialog/>
<div className="graphics">
<Graphic id={1}>
<Graphic id={2}>
<Graphic id={3}>
(multiple graphics)
</div>
</DownloadFlagsContext.Provider>
)
}
In my DownloadDialog, I trigger the downloads by setting the downloadFlags to the id's of each of the components that needs to be rendered.
const download = () => {
const newDownloadFlags = [] as any
for (let i = start; i < end; i++) {
newDownloadFlags.push(i)
}
setDownloadFlags(newDownloadFlags)
}
Now in each of the Graphic components, I trigger the download if the downloadFlags was changed and it contains it's id.
const render = () => {
const canvas = document.createElement("canvas")
/* code to render the graphic */
return canvas.toDataURL("image/png")
}
useEffect(() => {
if (downloadFlags.includes(props.id)) {
download("filename", render())
setDownloadFlags(downloadFlags.filter((s: string) => s !== props.id))
}
}, [downloadFlags])
The problem is that this code is triggering downloads. For example, if I set the download for 6 graphics it will result in downloading 21 images because every time that I change the downloadFlags, all of the components will get re-rendered, with the difference being that it includes one less id. So in total will download 6+5+4+3+2+1 images. Obviously this is very bad if I have a lot of graphics to download.
I would like to prevent the components from re-rendering so that it only downloads the first 6 times.
Also, I do not want to use a server. I want to download the images on the client side.
I have figured out a solution. In my app component, I keep track of the download ID's as well as a boolean flag for enabling the download:
export const DownloadIDsContext = React.createContext<any>(null)
export const DownloadFlagContext = React.createContext<any>(null)
const App: React.FunctionComponent = (props) => {
const [downloadIDs, setDownloadIDs] = useState([])
const [downloadFlag, setDownloadFlag] = useState(false)
return (
<DownloadFlagContext.Provider value={{downloadFlag, setDownloadFlag}}>
<DownloadURLsContext.Provider value={{downloadURLs, setDownloadURLs}}>
<DownloadDialog/>
<div className="graphics">
<Graphic id={1}>
<Graphic id={2}>
<Graphic id={3}>
(multiple graphics)
</div>
</DownloadURLsContext.Provider>
</DownloadFlagContext.Provider>
)
}
In my DownloadDialog, I set the boolean flag to true when I start a download.
const download = () => {
const newDownloadIDs = [] as any
for (let i = start; i < end; i++) {
newDownloadIDs.push(i)
}
setDownloadIDs(newDownloadIDs)
setDownloadFlag(true)
}
Now instead of checking for the download ID array in the child components, I only check for the boolean flag. Since it is set to false immediately it effectively only causes the components to render once.
useEffect(() => {
if (downloadFlag) {
if (downloadIDs.includes(props.id)) {
download("filename", render())
setDownloadIDs(downloadIDs.filter((s: string) => s !== props.id))
setDownloadFlag(false)
}
}
}, [downloadFlag])

How to use same page anchor tags to hide/show content in Next.js

The problem
The current project is using Next.js and this situation occurred: the content needs to be hidden or replaced, matching the current category selected. I want to do it without reloading or using another route to do so. And when the user presses F5 or reloads the page the content remains unchanged.
The attempts
Next.js' showcase page apparently is able to do so. In the docs, there's a feature called 'Shallow routing', which basically gives the possibility to update the URL without realoading the page. That's what i figured out for now. Any clues on how the content is changed to match the category?
Thanks!
You can load the content on the client based on the category passed in the URL fragment (# value) using window.location.hash.
Here's a minimal example of how to achieve this.
import React, { useState, useEffect } from 'react'
const data = {
'#news': 'News Data',
'#marketing': 'Marketing Data',
default: "Default Data"
}
const ShowCasePage = () => {
const router = useRouter()
const [categoryData, setCategoryData] = useState()
const changeCategory = (category) => {
// Trigger fragment change to fetch the new data
router.push(`/#${category}`, undefined, { shallow: true });
}
useEffect(() => {
const someData = data[window.location.hash] ?? data.default // Retrieve data based on URL fragment
setCategoryData(someData);
}, [router])
return (
<>
<div>Showcase Page</div>
<button onClick={() => changeCategory('news')}>News</button>
<button onClick={() => changeCategory('marketing')}>Marketing</button>
<div>{categoryData}</div>
</>
)
}
export default ShowCasePage

React Custom Hook make return Function executable on an Event

I have created a custom hook for handling the uploads to AWS s3 bucket but I am facing a small problem. I did not want my hook to execute the logic directly so I created an executable function that I am then returning. The only problem is that the state is not being set from inside the handleUpload function
Here is what I am trying to do:
import React, { useState } from "react";
const useS3Aws = () => {
const [isLoading, setIsLoading] = useState(false);
const [uploadedUrl, setUploadedUrl] = useState("");
const handleUpload = async (file: any, s3FolderPath: string) => {
setIsLoading(true); // the problem is here (state not being updated)
// handle the uploading with AWS s3
// code ......
// setting the state with returned live url from s3
setUploadedUrl("the live url"); // the problem is here (state not being updated)
console.log(uploadedUrl); // it prints an empty string
setIsLoading(true); // the problem is here (state not being updated)
};
return [
isLoading,
uploadedUrl,
(file: any, s3FolderPath: string) => handleUpload(file, s3FolderPath),
] as const;
};
const UploadSong = () => {
const [isLoading, uploadedUrl, handleUpload] = useS3Aws();
const handleSong = async () => {
const file = {
// object data
};
handleUpload(file, "music");
console.log(uploadedUrl); // it is empty
};
return (
<div>
<p>Live URL: {uploadedUrl ? uploadedUrl : "No Link"}</p>
<button onClick={() => handleSong()}>
Click me
</button>
</div>
);
}
export default UploadSong;
Playground Link
Your hook looks fine, but you aren't using it's returned values right.
<button onClick={() => handleSong}>
That doesn't actually call handleSong on click. This calls a function that merely returns the handleSong function on click.
You want:
<button onClick={() => handleSong()}>
Or if you don't want to pass arguments, you can just:
<button onClick={handleSong}>
One point where you may be confused here:
console.log(uploadedUrl); // it is empty
When you set state, it's asynchronous. State is being set properly, but any local variables will reflect the previous state, because the re-render has not yet happened. Usually this doesn't matter at all, because as you can see here the UI updates immediately when you click the working button.

Categories

Resources