You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
+++
slug = "exporting-binary-data-react"
reading_time = "5m"
summary = "Learn how to export binary data from your React application"
+++
This morning I worked on implementing a simple feature for Maffin to let users export their data directly from the website. It's the same as grabbing the saved file from your Google drive but this feature proves useful for users that play with the demo website and then want to export their data.
Implementing this I run into couple of gotchas so I decided it would be nice to share.
🔍 Research
I had no idea how to implement this so I did some quick research and arrived to this answer in SO. The proposed solution looks like this:
With this new knowledge, I decided to implement the following component that lets users download their data:
importReactfrom'react';import{BiExport}from'react-icons/bi';importpakofrom'pako';import{DataSourceContext}from'@/hooks';exportinterfaceImportButtonPropsextendsReact.ButtonHTMLAttributes<HTMLButtonElement>{className?: string;}exportdefaultfunctionImportButton({
className ='btn btn-primary',
...props}: ImportButtonProps): JSX.Element{const{ isLoaded, datasource }=React.useContext(DataSourceContext);constref=React.useRef<HTMLAnchorElement>(null);return(<>
// This is the button the user clicks to export the data
<buttonid="export-button"type="button"disabled={!isLoaded}className={className}onClick={()=>{if(ref.current){constrawBook=datasource?.sqljsManager.exportDatabase()asUint8Array;constblob=newBlob([rawBook],{type: 'application/vnd.sqlite3'});ref.current.href=window.URL.createObjectURL(blob);ref.current.click();}}}{...props}><BiExportclassName="inline-block align-middle mr-1"/><spanclassName="inline-block align-middle">Export</span></button>
// This is the hidden href that actually downloads the data. We click it from the button onClick.
<aclassName="hidden"href="/#"ref={ref}download="book.sqlite">
Download
</a></>);}
See that we create a button and a hidden anchor. In the onClick of the button is where all the interesting logic happens where:
We export the database.
We create a URL using the createObjectURL and set it as the href attribute of the anchor.
We trigger the download by "clicking" the anchor from the button onClick using ref.current.click.
🧪 Testing
For this post I also want to show the test because I found couple of issues with jest that needed some "research".
First is that createObjectURL is not defined so you need to mock it. I used (window.URL.createObjectURL as jest.Mock).mockReturnValue('blob:url');.
The other one is that navigation is not supported so, when in jest you simulate the click to the button, it will trigger an error. In order to workaround this, I added an even listener to the anchor that prevents the default behavior:
consthiddenLink=screen.getByRole('link');constmockClick=jest.fn();// So we can prove that it was clickedhiddenLink.addEventListener('click',(e)=>{e.preventDefault();mockClick();},);
This is the full test:
it('exports data from datasource',async()=>{(window.URL.createObjectURLasjest.Mock).mockReturnValue('blob:url');constmockDatasource={sqljsManager: {exportDatabase: jest.fn().mockReturnValue(newUint8Array([22,33]))asSqljsEntityManager['exportDatabase'],},}asDataSource;render(<DataSourceContext.Providervalue={{isLoaded: true,datasource: mockDatasource,}asDataSourceContextType}><ExportButton/></DataSourceContext.Provider>,);// https://github.com/jsdom/jsdom/issues/2112#issuecomment-926601210consthiddenLink=screen.getByRole('link');constmockClick=jest.fn();hiddenLink.addEventListener('click',(e)=>{e.preventDefault();mockClick();},);constbutton=awaitscreen.findByRole('button',{name: 'Export'});awaituserEvent.click(button);expect(hiddenLink).toHaveAttribute('href','blob:url');expect(mockClick).toBeCalled();expect(window.URL.createObjectURL).toBeCalledWith(newBlob([newUint8Array([22,33])],{type: 'application/vnd.sqlite3'}),);});
By steps, we do the following:
Render the element with a DataSourceProvider (which is our typeorm datasource containing the database).
Find the Export button and click it.
Check that the href has the right url and that it has been clicked.
Check that we've created the blob and its object URL with the right data.
💚 Conclusion
I'm quite happy with the result and how simple it has ended up being. As always, you can find the full PR we used to ship these changes here.
That's it! If you enjoyed the post feel free to react to it in the Github tracker (for now).
The text was updated successfully, but these errors were encountered:
+++
slug = "exporting-binary-data-react"
reading_time = "5m"
summary = "Learn how to export binary data from your React application"
+++
This morning I worked on implementing a simple feature for Maffin to let users export their data directly from the website. It's the same as grabbing the saved file from your Google drive but this feature proves useful for users that play with the demo website and then want to export their data.
Implementing this I run into couple of gotchas so I decided it would be nice to share.
🔍 Research
I had no idea how to implement this so I did some quick research and arrived to this answer in SO. The proposed solution looks like this:
There are two key things here:
🔧 Implementation
With this new knowledge, I decided to implement the following component that lets users download their data:
See that we create a button and a hidden anchor. In the onClick of the button is where all the interesting logic happens where:
createObjectURL
and set it as the href attribute of the anchor.ref.current.click
.🧪 Testing
For this post I also want to show the test because I found couple of issues with jest that needed some "research".
First is that
createObjectURL
is not defined so you need to mock it. I used(window.URL.createObjectURL as jest.Mock).mockReturnValue('blob:url');
.The other one is that
navigation
is not supported so, when in jest you simulate the click to the button, it will trigger an error. In order to workaround this, I added an even listener to the anchor that prevents the default behavior:This is the full test:
By steps, we do the following:
💚 Conclusion
I'm quite happy with the result and how simple it has ended up being. As always, you can find the full PR we used to ship these changes here.
That's it! If you enjoyed the post feel free to react to it in the Github tracker (for now).
The text was updated successfully, but these errors were encountered: