Skip to content
Advertisement

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.

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

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement