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.
Advertisement
Answer
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])