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])